mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-08 07:27:41 -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
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
@ -1980,6 +1986,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.
|
||||||
file.write_format("; generated by %s on %s\n", Slic3r::header_slic3r_generated().c_str(), Slic3r::Utils::local_timestamp().c_str());
|
file.write_format("; generated by %s on %s\n", Slic3r::header_slic3r_generated().c_str(), Slic3r::Utils::local_timestamp().c_str());
|
||||||
|
@ -2228,6 +2235,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);
|
||||||
|
@ -5292,6 +5364,27 @@ 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;
|
||||||
|
@ -5348,6 +5441,45 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||||
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);
|
||||||
|
|
|
@ -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>
|
||||||
|
@ -359,6 +361,19 @@ private:
|
||||||
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.
|
||||||
// Following structures sort extrusions by the extruder ID, by an order of objects and object islands.
|
// Following structures sort extrusions by the extruder ID, by an order of objects and object islands.
|
||||||
|
@ -541,6 +556,8 @@ private:
|
||||||
|
|
||||||
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;
|
||||||
|
|
114
src/libslic3r/GCode/AdaptivePAInterpolator.cpp
Normal file
114
src/libslic3r/GCode/AdaptivePAInterpolator.cpp
Normal 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
|
||||||
|
}
|
54
src/libslic3r/GCode/AdaptivePAInterpolator.hpp
Normal file
54
src/libslic3r/GCode/AdaptivePAInterpolator.hpp
Normal 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
|
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
|
85
src/libslic3r/GCode/AdaptivePAProcessor.hpp
Normal file
85
src/libslic3r/GCode/AdaptivePAProcessor.hpp
Normal 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
|
|
@ -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:"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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)]; }
|
||||||
|
|
100
src/libslic3r/GCode/PchipInterpolatorHelper.cpp
Normal file
100
src/libslic3r/GCode/PchipInterpolatorHelper.cpp
Normal 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];
|
||||||
|
}
|
76
src/libslic3r/GCode/PchipInterpolatorHelper.hpp
Normal file
76
src/libslic3r/GCode/PchipInterpolatorHelper.hpp
Normal 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
|
|
@ -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",
|
||||||
|
|
|
@ -1682,6 +1682,60 @@ void PrintConfigDef::init_fff_params()
|
||||||
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");
|
||||||
def->category = L("Quality");
|
def->category = L("Quality");
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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");
|
||||||
|
@ -3539,8 +3553,17 @@ void TabFilament::toggle_options()
|
||||||
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);
|
||||||
|
|
||||||
bool is_pellet_printer = cfg.opt_bool("pellet_modded_printer");
|
// Orca: adaptive pressure advance and calibration model
|
||||||
|
// If PA is not enabled, disable adaptive pressure advance and hide the model section
|
||||||
|
// If adaptive PA is not enabled, hide the adaptive PA model section
|
||||||
|
toggle_option("adaptive_pressure_advance", pa);
|
||||||
|
toggle_option("adaptive_pressure_advance_overhangs", pa);
|
||||||
|
bool has_adaptive_pa = m_config->opt_bool("adaptive_pressure_advance", 0);
|
||||||
|
toggle_line("adaptive_pressure_advance_overhangs", has_adaptive_pa && pa);
|
||||||
|
toggle_line("adaptive_pressure_advance_model", has_adaptive_pa && pa);
|
||||||
|
toggle_line("adaptive_pressure_advance_bridges", has_adaptive_pa && pa);
|
||||||
|
|
||||||
|
bool is_pellet_printer = cfg.opt_bool("pellet_modded_printer");
|
||||||
toggle_line("pellet_flow_coefficient", is_pellet_printer);
|
toggle_line("pellet_flow_coefficient", is_pellet_printer);
|
||||||
toggle_line("filament_diameter", !is_pellet_printer);
|
toggle_line("filament_diameter", !is_pellet_printer);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue