Add "First layer fan speed" option (per filament) (#14270)

Implement First layer fan speed
This commit is contained in:
Ioannis Giannakas
2026-06-30 10:48:58 +01:00
committed by GitHub
parent 03568b5200
commit 3c8c667dbd
6 changed files with 92 additions and 7 deletions

View File

@@ -744,6 +744,13 @@ std::string CoolingBuffer::apply_layer_cooldown(
int close_fan_the_first_x_layers = EXTRUDER_CONFIG(close_fan_the_first_x_layers);
// Is the fan speed ramp enabled?
int full_fan_speed_layer = EXTRUDER_CONFIG(full_fan_speed_layer);
// ORCA: explicit per-filament first-layer override (-1 = disabled, 0-100 = forced PWM percent on layer 0).
// The override is only honoured when the "No cooling for the first" gate is 0; otherwise the gate would
// force layers 1..N-1 to zero while the override sets layer 0 to a non-zero value, which is confusing
// and non-monotonic. The UI greys out and resets the value in that case, but we also guard here so
// legacy profiles loaded with both set are neutralised at the slicer level.
int initial_layer_fan_speed = EXTRUDER_CONFIG(initial_layer_fan_speed);
const bool has_initial_layer_override = initial_layer_fan_speed >= 0 && close_fan_the_first_x_layers <= 0;
supp_interface_fan_speed = EXTRUDER_CONFIG(support_material_interface_fan_speed);
// ORCA: previously a silent override forced `close_fan_the_first_x_layers` from 0 up to 1 whenever a ramp
@@ -751,7 +758,24 @@ std::string CoolingBuffer::apply_layer_cooldown(
// That hid the user's literal "no cooling for the first 0 layers" setting and produced a non-zero starting
// factor on the ramp denominator. The override has been removed: with N=0 and M>0 the ramp now genuinely
// starts on layer 0 at a factor of 1/M and reaches 100% at layer M-1, matching the intent of the option.
if (int(layer_id) >= close_fan_the_first_x_layers) {
//
// ORCA: First-layer hard override (`initial_layer_fan_speed`). When the user has set this option to a
// value >= 0, layer 0 emits exactly that percentage so the entire first layer
// is at one stable fan speed. The override wins over the `close_fan_the_first_x_layers` gate when
// layer_id == 0. From layer 1 onwards the regular logic resumes.
if (has_initial_layer_override && layer_id == 0) {
fan_speed_new = initial_layer_fan_speed;
overhang_fan_speed = initial_layer_fan_speed;
overhang_fan_control = false;
internal_bridge_fan_speed = initial_layer_fan_speed;
internal_bridge_fan_control = false;
supp_interface_fan_speed = initial_layer_fan_speed;
supp_interface_fan_control = false;
ironing_fan_speed = initial_layer_fan_speed;
ironing_fan_control = false;
// additional_fan_speed_new is left at its configured value (auxiliary fan is independent of the
// part-cooling override).
} else if (int(layer_id) >= close_fan_the_first_x_layers) {
float fan_max_speed = EXTRUDER_CONFIG(fan_max_speed);
float slow_down_layer_time = float(EXTRUDER_CONFIG(slow_down_layer_time));
float fan_cooling_layer_time = float(EXTRUDER_CONFIG(fan_cooling_layer_time));
@@ -769,10 +793,25 @@ std::string CoolingBuffer::apply_layer_cooldown(
//}
overhang_fan_speed = EXTRUDER_CONFIG(overhang_fan_speed);
if (int(layer_id) >= close_fan_the_first_x_layers && int(layer_id) + 1 < full_fan_speed_layer) {
// Ramp up the fan speed from close_fan_the_first_x_layers to full_fan_speed_layer.
float factor = float(int(layer_id + 1) - close_fan_the_first_x_layers) / float(full_fan_speed_layer - close_fan_the_first_x_layers);
fan_speed_new = std::clamp(int(float(fan_speed_new) * factor + 0.5f), 0, 255);
overhang_fan_speed = std::clamp(int(float(overhang_fan_speed) * factor + 0.5f), 0, 255);
if (has_initial_layer_override && close_fan_the_first_x_layers == 0 && full_fan_speed_layer > 1) {
// ORCA: Option-B anchored ramp. When the first-layer override is configured and there's
// no "no cooling" gate, the ramp interpolates linearly from `initial_layer_fan_speed` on
// layer 0 up to the computed target on layer `full_fan_speed_layer - 1` instead of
// scaling the target by `factor` from zero. Layer 0 itself is handled by the override
// branch above; this branch only runs for layer_id >= 1, but the formula uses t = 0 at
// layer 0 conceptually so the curve is continuous. Guarantees a monotonic transition
// even when the override is larger than the natural 1/M starting value.
const float anchor = float(initial_layer_fan_speed);
const float denom = float(full_fan_speed_layer - 1);
const float t = float(int(layer_id)) / denom;
fan_speed_new = std::clamp(int(anchor + t * (float(fan_speed_new) - anchor) + 0.5f), 0, 255);
overhang_fan_speed = std::clamp(int(anchor + t * (float(overhang_fan_speed) - anchor) + 0.5f), 0, 255);
} else {
// Ramp up the fan speed from close_fan_the_first_x_layers to full_fan_speed_layer.
float factor = float(int(layer_id + 1) - close_fan_the_first_x_layers) / float(full_fan_speed_layer - close_fan_the_first_x_layers);
fan_speed_new = std::clamp(int(float(fan_speed_new) * factor + 0.5f), 0, 255);
overhang_fan_speed = std::clamp(int(float(overhang_fan_speed) * factor + 0.5f), 0, 255);
}
}
supp_interface_fan_speed = EXTRUDER_CONFIG(support_material_interface_fan_speed);
supp_interface_fan_control = supp_interface_fan_speed >= 0;