Fix arc fitting with overhang fan control (#16297)

This commit is contained in:
Kiss Lorand
2026-10-11 13:09:16 -03:00
committed by GitHub
parent 8145b0792f
commit 52eeb0f228
2 changed files with 98 additions and 27 deletions
+39 -27
View File
@@ -8968,6 +8968,16 @@ static float overhang_fan_overlap_threshold(int overhang_fan_threshold)
}
}
static bool overhang_fan_active(float overlap, int overhang_fan_threshold, ExtrusionRole role)
{
if (role == erBridgeInfill || role == erOverhangPerimeter)
return true;
if (overhang_fan_threshold == Overhang_threshold_none)
return is_external_perimeter(role);
const float overlap_threshold = overhang_fan_overlap_threshold(overhang_fan_threshold);
return overlap_threshold >= 0.f && overlap <= overlap_threshold;
}
std::string GCode::_extrude(const ExtrusionPath &path, const std::string &path_description, double speed)
{
std::string gcode;
@@ -9326,7 +9336,10 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string &path_d
}
}
bool variable_speed = false;
bool requires_segmented_extrusion = false;
// A uniform fan state can be represented by the normal path-wide marker. Only a
// transition within the path requires point-by-point emission.
bool path_overhang_fan_active = false;
std::vector<ProcessedPoint> new_points {};
const bool need_overhang_detection = NOZZLE_CONFIG(enable_overhang_speed) ||
@@ -9398,13 +9411,21 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string &path_d
ref_speed, speed, NOZZLE_CONFIG(slowdown_for_curled_perimeters),
fan_overlap_threshold);
}
variable_speed = std::any_of(new_points.begin(), new_points.end(),
[speed](const ProcessedPoint &p) { return fabs(double(p.speed) - speed) > 1; }); // Ignore small speed variations (under 1mm/sec)
if (FILAMENT_CONFIG(enable_overhang_bridge_fan) && m_enable_cooling_markers) {
if (!NOZZLE_CONFIG(enable_overhang_speed))
for (ProcessedPoint &point : new_points)
point.speed = speed;
variable_speed = new_points.size() > 1;
if (!NOZZLE_CONFIG(enable_overhang_speed))
for (ProcessedPoint &point : new_points)
point.speed = speed;
requires_segmented_extrusion = std::any_of(new_points.begin(), new_points.end(),
[speed](const ProcessedPoint &point) { return fabs(double(point.speed) - speed) > 1; }); // Ignore small speed variations (under 1mm/sec)
if (FILAMENT_CONFIG(enable_overhang_bridge_fan) && m_enable_cooling_markers && fan_overlap_threshold >= 0.f &&
!new_points.empty()) {
path_overhang_fan_active = new_points.front().overlap <= fan_overlap_threshold;
// An overhang-fan state change needs separate extrusion moves so the cooling buffer can insert
// the corresponding fan command between them.
requires_segmented_extrusion = requires_segmented_extrusion ||
std::adjacent_find(new_points.begin(), new_points.end(), [fan_overlap_threshold](const ProcessedPoint &previous,
const ProcessedPoint &current) {
return (previous.overlap <= fan_overlap_threshold) != (current.overlap <= fan_overlap_threshold);
}) != new_points.end();
}
}
@@ -9542,22 +9563,17 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string &path_d
auto overhang_fan_threshold = FILAMENT_CONFIG(overhang_fan_threshold);
auto enable_overhang_bridge_fan = FILAMENT_CONFIG(enable_overhang_bridge_fan);
if (enable_overhang_bridge_fan &&
(path.role() == erBridgeInfill || path.role() == erOverhangPerimeter ||
(overhang_fan_threshold == Overhang_threshold_none && is_external_perimeter(path.role()))))
path_overhang_fan_active = true;
// { "0%", Overhang_threshold_none },
// { "10%", Overhang_threshold_1_4 },
// { "25%", Overhang_threshold_2_4 },
// { "50%", Overhang_threshold_3_4 },
// { "75%", Overhang_threshold_4_4 },
// { "95%", Overhang_threshold_bridge }
auto check_overhang_fan = [&overhang_fan_threshold](float overlap, ExtrusionRole role) {
if (role == erBridgeInfill || role == erOverhangPerimeter) { // ORCA: Split out bridge infill to internal and external to apply separate fan settings
return true;
}
if (overhang_fan_threshold == Overhang_threshold_none)
return is_external_perimeter(role);
const float overlap_threshold = overhang_fan_overlap_threshold(overhang_fan_threshold);
return overlap_threshold >= 0.f && overlap <= overlap_threshold;
};
std::string comment;
if (m_enable_cooling_markers) {
comment = ";_EXTRUDE_SET_SPEED";
@@ -9628,7 +9644,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string &path_d
}
};
if (!variable_speed) {
if (!requires_segmented_extrusion) {
// 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
@@ -9678,11 +9694,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string &path_d
{
if (m_enable_cooling_markers) {
if (enable_overhang_bridge_fan) {
// BBS: Overhang_threshold_none means Overhang_threshold_1_4 and forcing cooling for all external
// perimeter
append_role_based_fan_marker(erOverhangPerimeter, "_OVERHANG"sv,
(overhang_fan_threshold == Overhang_threshold_none && is_external_perimeter(path.role())) ||
(path.role() == erBridgeInfill || path.role() == erOverhangPerimeter)); // ORCA: Add support for separate internal bridge fan speed control
append_role_based_fan_marker(erOverhangPerimeter, "_OVERHANG"sv, path_overhang_fan_active);
// ORCA: Add support for separate internal bridge fan speed control
append_role_based_fan_marker(erInternalBridgeInfill, "_INTERNAL_BRIDGE"sv, path.role() == erInternalBridgeInfill);
@@ -9832,7 +9844,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string &path_d
bool pre_fan_enabled = false;
bool cur_fan_enabled = false;
if( m_enable_cooling_markers && enable_overhang_bridge_fan)
pre_fan_enabled = check_overhang_fan(new_points[0].overlap, path.role());
pre_fan_enabled = overhang_fan_active(new_points[0].overlap, overhang_fan_threshold, path.role());
if(path.role() == erInternalBridgeInfill) // ORCA: Add support for separate internal bridge fan speed control
pre_fan_enabled = true;
@@ -9846,7 +9858,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string &path_d
tag_belt_band((pre_processed_point.p.x() + processed_point.p.x()) / 2, (pre_processed_point.p.y() + processed_point.p.y()) / 2);
if (m_enable_cooling_markers) {
if (enable_overhang_bridge_fan) {
cur_fan_enabled = check_overhang_fan(processed_point.overlap, path.role());
cur_fan_enabled = overhang_fan_active(processed_point.overlap, overhang_fan_threshold, path.role());
append_role_based_fan_marker(erOverhangPerimeter, "_OVERHANG"sv, pre_fan_enabled && cur_fan_enabled);
pre_fan_enabled = cur_fan_enabled;
@@ -9949,7 +9961,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string &path_d
m_writer.extrude_to_xyz(gcode, dest3d, dE * e_ratio, flow_description.empty() ? description : flow_description);
}
// Inline farthest-point snapshot on the variable-speed emission path. Inert unless the
// Inline farthest-point snapshot on the segmented emission path. Inert unless the
// farthest-point subsystem is on, matching the other paths.
check_and_insert_timelapse(processed_point.p.to_point());
+59
View File
@@ -4,6 +4,7 @@
#include "test_helpers.hpp"
#include <algorithm>
#include <cstddef>
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/Config.hpp"
#include "libslic3r/TriangleMesh.hpp"
@@ -35,6 +36,64 @@ TEST_CASE("Cooling consumes its internal speed markers", "[Cooling]")
CHECK(gcode.find(";_EXTRUDE_SET_SPEED") == std::string::npos);
}
TEST_CASE("Arc fitting remains active with overhang fan control", "[Cooling][Regression]")
{
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
config.set_deserialize_strict({
{ "enable_arc_fitting", true },
{ "enable_overhang_bridge_fan", true },
{ "enable_overhang_speed", true },
{ "initial_layer_print_height", 0.2 },
{ "layer_height", 0.2 },
{ "slow_down_for_layer_cooling", false },
});
config.set_key_value("fan_max_speed", new ConfigOptionFloats{20.0});
config.set_key_value("fan_min_speed", new ConfigOptionFloats{20.0});
config.set_key_value("overhang_fan_speed", new ConfigOptionInts{100});
config.set_key_value("overhang_fan_threshold", new ConfigOptionEnumsGeneric{Overhang_threshold_2_4});
config.set_key_value("layer_change_gcode", new ConfigOptionString{";TEST_LAYER=[layer_num]"});
const auto has_arc_after_first_layer = [](const std::string &gcode) {
std::istringstream input(gcode);
std::string line;
size_t layers_seen = 0;
while (std::getline(input, line)) {
if (line.rfind(";TEST_LAYER=", 0) == 0)
++layers_seen;
else if (layers_seen > 1 && (line.rfind("G2 ", 0) == 0 || line.rfind("G3 ", 0) == 0))
return true;
}
return false;
};
const auto has_arc_with_overhang_fan = [](const std::string &gcode) {
std::istringstream input(gcode);
std::string line;
bool overhang_fan_active = false;
bool outer_wall = false;
while (std::getline(input, line)) {
if (line.rfind("M106 S255", 0) == 0)
overhang_fan_active = true;
else if (line.rfind("M106 S0", 0) == 0 || line.rfind("M107", 0) == 0)
overhang_fan_active = false;
else if (line.rfind(";TYPE:", 0) == 0)
outer_wall = line == ";TYPE:Outer wall";
else if (overhang_fan_active && outer_wall && (line.rfind("G2 ", 0) == 0 || line.rfind("G3 ", 0) == 0))
return true;
}
return false;
};
const std::string with_overhang_speed = slice({ make_cylinder(25.0, 10.0, 2.0 * PI / 72.0) }, config);
CHECK(has_arc_after_first_layer(with_overhang_speed));
config.set_deserialize_strict({ { "enable_overhang_speed", false } });
const std::string without_overhang_speed = slice({ make_cylinder(25.0, 10.0, 2.0 * PI / 72.0) }, config);
CHECK(has_arc_after_first_layer(without_overhang_speed));
const std::string uniform_overhang_gcode = slice({ make_sphere(50.0, 2.0 * PI / 72.0) }, config);
CHECK(has_arc_with_overhang_fan(uniform_overhang_gcode));
}
TEST_CASE("Overhang fan transitions do not depend on overhang speed", "[Cooling][Regression]")
{
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();