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:
Ioannis Giannakas 2024-07-28 15:52:08 +01:00 committed by GitHub
parent 543a1e5c37
commit 529c44d8e3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 1067 additions and 15 deletions

View file

@ -156,6 +156,12 @@ set(lisbslic3r_sources
GCode/RetractWhenCrossingPerimeters.hpp GCode/RetractWhenCrossingPerimeters.hpp
GCode/SmallAreaInfillFlowCompensator.cpp GCode/SmallAreaInfillFlowCompensator.cpp
GCode/SmallAreaInfillFlowCompensator.hpp 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.cpp
GCode/SpiralVase.hpp GCode/SpiralVase.hpp
GCode/SeamPlacer.cpp GCode/SeamPlacer.cpp

View file

@ -668,6 +668,9 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
// SoftFever: set new PA for new filament // SoftFever: set new PA for new filament
if (gcodegen.config().enable_pressure_advance.get_at(new_extruder_id)) { 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)); 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. // A phony move to the end position at the wipe tower.
@ -856,6 +859,9 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
// SoftFever: set new PA for new filament // SoftFever: set new PA for new filament
if (new_extruder_id != -1 && gcodegen.config().enable_pressure_advance.get_at(new_extruder_id)) { 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)); 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. // 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()) if (!print.config().small_area_infill_flow_compensation_model.empty())
m_small_area_infill_flow_compensator = make_unique<SmallAreaInfillFlowCompensator>(print.config()); m_small_area_infill_flow_compensator = make_unique<SmallAreaInfillFlowCompensator>(print.config());
file.write_format("; HEADER_BLOCK_START\n"); file.write_format("; HEADER_BLOCK_START\n");
// Write information on the generator. // Write information on the generator.
@ -2227,6 +2234,9 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
m_cooling_buffer = make_unique<CoolingBuffer>(*this); m_cooling_buffer = make_unique<CoolingBuffer>(*this);
m_cooling_buffer->set_current_extruder(initial_extruder_id); m_cooling_buffer->set_current_extruder(initial_extruder_id);
// Orca: Initialise AdaptivePA processor filter
m_pa_processor = std::make_unique<AdaptivePAProcessor>(*this, tool_ordering.all_extruders());
// Emit machine envelope limits for the Marlin firmware. // Emit machine envelope limits for the Marlin firmware.
this->print_machine_envelope(file, print); this->print_machine_envelope(file, print);
@ -2870,6 +2880,12 @@ void GCode::process_layers(
return in.gcode; return in.gcode;
return cooling_buffer.process_layer(std::move(in.gcode), in.layer_id, in.cooling_buffer_flush); return cooling_buffer.process_layer(std::move(in.gcode), in.layer_id, in.cooling_buffer_flush);
}); });
const auto pa_processor_filter = tbb::make_filter<std::string, std::string>(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<std::string, void>(slic3r_tbb_filtermode::serial_in_order, const auto output = tbb::make_filter<std::string, void>(slic3r_tbb_filtermode::serial_in_order,
[&output_stream](std::string s) { output_stream.write(s); } [&output_stream](std::string s) { output_stream.write(s); }
); );
@ -2900,9 +2916,9 @@ void GCode::process_layers(
else if (m_spiral_vase) else if (m_spiral_vase)
tbb::parallel_pipeline(12, generator & spiral_mode & cooling & fan_mover & output); tbb::parallel_pipeline(12, generator & spiral_mode & cooling & fan_mover & output);
else if (m_pressure_equalizer) 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 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: // Process all layers of a single object instance (sequential mode) with a parallel pipeline:
@ -4548,6 +4564,7 @@ static std::unique_ptr<EdgeGrid::Grid> calculate_layer_edge_grid(const Layer& la
std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, double speed, const ExtrusionEntitiesPtr& region_perimeters) 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 // get a copy; don't modify the orientation of the original loop object otherwise
// next copies (if any) would not detect the correct orientation // 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){ if(discoveredTouchingLines > 1){
// use extrude instead of travel_to_xy to trigger the unretract // use extrude instead of travel_to_xy to trigger the unretract
ExtrusionPath fake_path_wipe(Polyline{pt, current_point}, paths.front()); 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.mm3_per_mm = 0;
//fake_path_wipe.set_extrusion_role(erExternalPerimeter);
gcode += extrude_path(fake_path_wipe, "move inwards before retraction/seam", speed); 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; 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<double>(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) { if (!enable_seam_slope) {
for (ExtrusionPaths::iterator path = paths.begin(); path != paths.end(); ++path) { for (ExtrusionPaths::iterator path = paths.begin(); path != paths.end(); ++path) {
gcode += this->_extrude(*path, description, speed_for_path(*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 { } else {
// Create seam slope // Create seam slope
@ -4719,6 +4761,10 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
// Then extrude it // Then extrude it
for (const auto& p : new_loop.get_all_paths()) { for (const auto& p : new_loop.get_all_paths()) {
gcode += this->_extrude(*p, description, speed_for_path(*p)); 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 // Fix path for wipe
@ -4790,8 +4836,31 @@ std::string GCode::extrude_multi_path(ExtrusionMultiPath multipath, std::string
{ {
// extrude along the path // extrude along the path
std::string gcode; 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<double>(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); 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 // BBS
if (m_wipe.enable) { 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) 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); std::string gcode = this->_extrude(path, description, speed);
if (m_wipe.enable) { if (m_wipe.enable) {
m_wipe.path = std::move(path.polyline); 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 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 //Orca: process custom gcode for extrusion role change
if (path.role() != m_last_extrusion_role && !m_config.change_extrusion_role_gcode.value.empty()) { if (path.role() != m_last_extrusion_role && !m_config.change_extrusion_role_gcode.value.empty()) {
DynamicConfig config; 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); sprintf(buf, ";%s%g\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height).c_str(), m_last_height);
gcode += buf; 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 overhang_fan_threshold = EXTRUDER_CONFIG(overhang_fan_threshold);
auto enable_overhang_bridge_fan = EXTRUDER_CONFIG(enable_overhang_bridge_fan); 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) { if (!variable_speed) {
// F is mm per minute. // 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); gcode += m_writer.set_speed(F, "", comment);
{ {
if (m_enable_cooling_markers) { if (m_enable_cooling_markers) {
@ -5591,6 +5771,52 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
continue; continue;
path_length += line_length; path_length += line_length;
double new_speed = pre_processed_point.speed * 60.0; 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) { if (last_set_speed != new_speed) {
gcode += m_writer.set_speed(new_speed, "", comment); gcode += m_writer.set_speed(new_speed, "", comment);
last_set_speed = new_speed; 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)) { if (m_config.enable_pressure_advance.get_at(extruder_id)) {
gcode += m_writer.set_pressure_advance(m_config.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); gcode += m_writer.toolchange(extruder_id);

View file

@ -24,6 +24,8 @@
#include "GCode/PressureEqualizer.hpp" #include "GCode/PressureEqualizer.hpp"
#include "GCode/SmallAreaInfillFlowCompensator.hpp" #include "GCode/SmallAreaInfillFlowCompensator.hpp"
// ORCA: post processor below used for Dynamic Pressure advance
#include "GCode/AdaptivePAProcessor.hpp"
#include <memory> #include <memory>
#include <map> #include <map>
@ -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_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_multi_path(ExtrusionMultiPath multipath, std::string description = "", double speed = -1.);
std::string extrude_path(ExtrusionPath path, 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 // Extruding multiple objects with soluble / non-soluble / combined supports
// on a multi-material printer, trying to minimize tool switches. // on a multi-material printer, trying to minimize tool switches.
@ -540,11 +555,13 @@ private:
std::unique_ptr<SpiralVase> m_spiral_vase; std::unique_ptr<SpiralVase> m_spiral_vase;
std::unique_ptr<PressureEqualizer> m_pressure_equalizer; std::unique_ptr<PressureEqualizer> m_pressure_equalizer;
std::unique_ptr<AdaptivePAProcessor> m_pa_processor;
std::unique_ptr<WipeTowerIntegration> m_wipe_tower; std::unique_ptr<WipeTowerIntegration> m_wipe_tower;
std::unique_ptr<SmallAreaInfillFlowCompensator> m_small_area_infill_flow_compensator; std::unique_ptr<SmallAreaInfillFlowCompensator> m_small_area_infill_flow_compensator;
// Heights (print_z) at which the skirt has already been extruded. // Heights (print_z) at which the skirt has already been extruded.
std::vector<coordf_t> m_skirt_done; std::vector<coordf_t> m_skirt_done;
// Has the brim been extruded already? Brim is being extruded only for the first object of a multi-object print. // Has the brim been extruded already? Brim is being extruded only for the first object of a multi-object print.

View file

@ -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 <stdexcept>
#include <cmath>
#include <algorithm>
#include <sstream>
/**
* @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<double, std::vector<std::pair<double, double>>> 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<double> flowRates;
std::vector<double> 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<double> pa_values;
std::vector<double> 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
}

View file

@ -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 <vector>
#include <string>
#include <map>
#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<double, PchipInterpolatorHelper> flow_interpolators_; ///< Map each acceleration to a flow-rate-to-PA interpolator.
std::vector<double> accelerations_; ///< Store unique accelerations.
bool m_isInitialised;
};
#endif // ADAPTIVEPAINTERPOLATOR_HPP

View 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

View file

@ -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 <string>
#include <sstream>
#include <regex>
#include <memory>
#include <map>
#include <vector>
#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<unsigned int> &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<unsigned int, std::unique_ptr<AdaptivePAInterpolator>> 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

View file

@ -68,7 +68,8 @@ const std::vector<std::string> GCodeProcessor::Reserved_Tags = {
" MANUAL_TOOL_CHANGE ", " MANUAL_TOOL_CHANGE ",
"_DURING_PRINT_EXHAUST_FAN", "_DURING_PRINT_EXHAUST_FAN",
" WIPE_TOWER_START", " WIPE_TOWER_START",
" WIPE_TOWER_END" " WIPE_TOWER_END",
" PA_CHANGE:"
}; };
const std::vector<std::string> GCodeProcessor::Reserved_Tags_compatible = { const std::vector<std::string> GCodeProcessor::Reserved_Tags_compatible = {
@ -88,7 +89,8 @@ const std::vector<std::string> GCodeProcessor::Reserved_Tags_compatible = {
" MANUAL_TOOL_CHANGE ", " MANUAL_TOOL_CHANGE ",
"_DURING_PRINT_EXHAUST_FAN", "_DURING_PRINT_EXHAUST_FAN",
" WIPE_TOWER_START", " WIPE_TOWER_START",
" WIPE_TOWER_END" " WIPE_TOWER_END",
" PA_CHANGE:"
}; };

View file

@ -291,6 +291,7 @@ namespace Slic3r {
During_Print_Exhaust_Fan, During_Print_Exhaust_Fan,
Wipe_Tower_Start, Wipe_Tower_Start,
Wipe_Tower_End, Wipe_Tower_End,
PA_Change,
}; };
static const std::string& reserved_tag(ETags tag) { return s_IsBBLPrinter ? Reserved_Tags[static_cast<unsigned char>(tag)] : Reserved_Tags_compatible[static_cast<unsigned char>(tag)]; } static const std::string& reserved_tag(ETags tag) { return s_IsBBLPrinter ? Reserved_Tags[static_cast<unsigned char>(tag)] : Reserved_Tags_compatible[static_cast<unsigned char>(tag)]; }

View file

@ -0,0 +1,100 @@
// PchipInterpolatorHelper.cpp
// OrcaSlicer
//
// Implementation file for the PchipInterpolatorHelper class
#include "PchipInterpolatorHelper.hpp"
#include <stdexcept>
#include <cmath>
#include <algorithm>
/**
* @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<double>& x, const std::vector<double>& 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<double>& x, const std::vector<double>& 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<std::pair<double, double>> 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];
}

View file

@ -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 <vector>
/**
* @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<double>& x, const std::vector<double>& 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<double>& x, const std::vector<double>& 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<double> x_; ///< The x-coordinates of the data points.
std::vector<double> y_; ///< The y-coordinates of the data points.
std::vector<double> h_; ///< The differences between successive x-coordinates.
std::vector<double> delta_; ///< The slopes of the segments between successive data points.
std::vector<double> 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

View file

@ -840,7 +840,7 @@ static std::vector<std::string> s_Preset_filament_options {
"filament_wipe_distance", "additional_cooling_fan_speed", "filament_wipe_distance", "additional_cooling_fan_speed",
"nozzle_temperature_range_low", "nozzle_temperature_range_high", "nozzle_temperature_range_low", "nozzle_temperature_range_high",
//SoftFever //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_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_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", "filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters",

View file

@ -1681,6 +1681,60 @@ void PrintConfigDef::init_fff_params()
def->max = 2; def->max = 2;
def->mode = comAdvanced; def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloats { 0.02 }); 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 = this->add("line_width", coFloatOrPercent);
def->label = L("Default"); def->label = L("Default");

View file

@ -1031,6 +1031,12 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionFloats, filament_flow_ratio)) ((ConfigOptionFloats, filament_flow_ratio))
((ConfigOptionBools, enable_pressure_advance)) ((ConfigOptionBools, enable_pressure_advance))
((ConfigOptionFloats, 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)) ((ConfigOptionFloat, fan_kickstart))
((ConfigOptionBool, fan_speedup_overhangs)) ((ConfigOptionBool, fan_speedup_overhangs))
((ConfigOptionFloat, fan_speedup_time)) ((ConfigOptionFloat, fan_speedup_time))

View file

@ -3223,11 +3223,6 @@ void TabFilament::build()
optgroup->append_single_option_line("required_nozzle_HRC"); optgroup->append_single_option_line("required_nozzle_HRC");
optgroup->append_single_option_line("default_filament_colour"); optgroup->append_single_option_line("default_filament_colour");
optgroup->append_single_option_line("filament_diameter"); 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_density");
optgroup->append_single_option_line("filament_shrink"); optgroup->append_single_option_line("filament_shrink");
@ -3249,6 +3244,25 @@ void TabFilament::build()
on_value_change(opt_key, value); 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 = page->new_optgroup(L("Print chamber temperature"), L"param_chamber_temp");
optgroup->append_single_option_line("chamber_temperature", "chamber-temperature"); 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("cool_plate_temp_initial_layer", support_multi_bed_types );
toggle_line("eng_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); 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"); bool is_pellet_printer = cfg.opt_bool("pellet_modded_printer");
toggle_line("pellet_flow_coefficient", is_pellet_printer); toggle_line("pellet_flow_coefficient", is_pellet_printer);
toggle_line("filament_diameter", !is_pellet_printer); toggle_line("filament_diameter", !is_pellet_printer);
} }