diff --git a/src/libslic3r/GCode/CoolingBuffer.cpp b/src/libslic3r/GCode/CoolingBuffer.cpp index edcf60cdb6..c0c79be466 100644 --- a/src/libslic3r/GCode/CoolingBuffer.cpp +++ b/src/libslic3r/GCode/CoolingBuffer.cpp @@ -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; diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index cc429a026d..bc728f62e5 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1318,7 +1318,7 @@ static std::vector s_Preset_filament_options {/*"filament_colour", // "bed_type", //BBS:temperature_vitrification "temperature_vitrification", "reduce_fan_stop_start_freq","dont_slow_down_outer_wall", "slow_down_for_layer_cooling", "fan_min_speed", - "fan_max_speed", "enable_overhang_bridge_fan", "overhang_fan_speed", "overhang_fan_threshold", "close_fan_the_first_x_layers", "close_additional_fan_first_x_layers", "first_x_layer_fan_speed", "full_fan_speed_layer", "additional_fan_full_speed_layer", "fan_cooling_layer_time", "slow_down_layer_time", "slow_down_min_speed", + "fan_max_speed", "enable_overhang_bridge_fan", "overhang_fan_speed", "overhang_fan_threshold", "close_fan_the_first_x_layers", "close_additional_fan_first_x_layers", "first_x_layer_fan_speed", "full_fan_speed_layer", "initial_layer_fan_speed", "additional_fan_full_speed_layer", "fan_cooling_layer_time", "slow_down_layer_time", "slow_down_min_speed", "filament_start_gcode", "filament_end_gcode", "filament_change_extrusion_role_gcode", //exhaust fan control "activate_air_filtration","activate_air_filtration_during_print","activate_air_filtration_on_completion","during_print_exhaust_fan_speed","complete_print_exhaust_fan_speed", diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 90866bf2b7..a676d24cc0 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -134,6 +134,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n "dont_slow_down_outer_wall", "fan_cooling_layer_time", "full_fan_speed_layer", + "initial_layer_fan_speed", "fan_kickstart", "part_cooling_fan_min_pwm", "fan_speedup_overhangs", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index c6758e58f1..0369c70ee3 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3348,7 +3348,25 @@ void PrintConfigDef::init_fff_params() def->max = 1000; def->mode = comAdvanced; def->set_default_value(new ConfigOptionInts { 0 }); - + + // ORCA: explicit override for the part cooling fan speed on the first printed layer. + def = this->add("initial_layer_fan_speed", coInts); + def->label = L("First layer fan speed"); + def->tooltip = L("Sets an exact fan speed for the first layer, overriding all other cooling settings. " + "Useful for protecting 3D-printed toolhead parts (e.g. Voron-style ABS/ASA ducts) from " + "a hot bed. A small amount of airflow cools the ducts down, without using full cooling that " + "may in certain conditions hurt first-layer adhesion." + "\nFrom the second layer onwards, normal cooling resumes." + "\nIf \"Full fan speed at layer\" is also set, the fan ramps smoothly from this value " + "on the first layer up to your target by the chosen layer." + "\nOnly available when \"No cooling for the first\" is 0." + "\nSet to -1 to disable it."); + def->sidetext = "%"; + def->min = -1; + def->max = 100; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionInts { -1 }); + def = this->add("support_material_interface_fan_speed", coInts); def->label = L("Support interface fan speed"); def->tooltip = L("This part cooling fan speed is applied when printing support interfaces. Setting this parameter to a higher than regular speed " diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index bc3cc4af8c..b51eef8aa3 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1555,6 +1555,8 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionFloatsNullable, initial_layer_infill_speed)) ((ConfigOptionInts, nozzle_temperature_initial_layer)) ((ConfigOptionInts, full_fan_speed_layer)) + // ORCA: explicit override for the part cooling fan speed on the first printed layer. + ((ConfigOptionInts, initial_layer_fan_speed)) ((ConfigOptionFloats, fan_max_speed)) ((ConfigOptionFloats, max_layer_height)) ((ConfigOptionFloats, fan_min_speed)) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 2f2940d60a..39512eb396 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -4368,6 +4368,8 @@ void TabFilament::build() //optgroup->append_line(line); optgroup = page->new_optgroup(L("Cooling for specific layer"), L"param_cooling_specific_layer"); optgroup->append_single_option_line("close_fan_the_first_x_layers", "material_cooling#no-cooling-for-the-first"); + // ORCA: explicit override for the part cooling fan on layer 0; also anchors the ramp when "Full fan speed at layer" is set. + optgroup->append_single_option_line("initial_layer_fan_speed", "material_cooling#first-layer-fan-speed"); optgroup->append_single_option_line("full_fan_speed_layer", "material_cooling#full-fan-speed-at-layer"); optgroup = page->new_optgroup(L("Part cooling fan"), L"param_cooling_part_fan"); @@ -4589,6 +4591,29 @@ void TabFilament::toggle_options() bool has_slow_down_for_layer_cooling = m_config->opt_bool("slow_down_for_layer_cooling", 0); toggle_option("dont_slow_down_outer_wall", has_slow_down_for_layer_cooling); + // ORCA: First layer fan speed override only makes sense when no layers are gated off ("No cooling for + // the first" == 0). Otherwise the override would set layer 0 to a non-zero value while the gate forces + // layers 1..N-1 to zero, producing a confusing non-monotonic profile. When the gate is active we both + // grey out the UI line and force the underlying value to -1 so the cooling buffer never enters the + // override branch. + const int close_fan_first_n = m_config->opt_int("close_fan_the_first_x_layers", 0); + const bool initial_layer_fan_speed_enabled = close_fan_first_n <= 0; + toggle_line("initial_layer_fan_speed", initial_layer_fan_speed_enabled); + if (!initial_layer_fan_speed_enabled) { + if (auto* opt = dynamic_cast(m_config->option("initial_layer_fan_speed"))) { + bool needs_reset = false; + for (int v : opt->values) { + if (v != -1) { needs_reset = true; break; } + } + if (needs_reset) { + std::vector reset_values(opt->values.size(), -1); + m_config->set_key_value("initial_layer_fan_speed", new ConfigOptionInts(reset_values)); + update_dirty(); + reload_config(); + } + } + } + toggle_line("additional_cooling_fan_speed", printer_cfg.opt_bool("auxiliary_fan")); bool support_air_filtration = printer_cfg.opt_bool("support_air_filtration");