diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 9716ecc208..40650c5092 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -484,6 +484,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ + size_t first_object_layer_id = this->object()->get_layer(0)->id(); for (SurfaceFill &surface_fill : surface_fills) { //skip patterns for which additional input is nullptr switch (surface_fill.params.pattern) { @@ -496,7 +497,10 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: // Create the filler object. std::unique_ptr f = std::unique_ptr(Fill::new_from_type(surface_fill.params.pattern)); f->set_bounding_box(bbox); - f->layer_id = this->id(); + // Layer ID is used for orienting the infill in alternating directions. + // Layer::id() returns layer ID including raft layers, subtract them to make the infill direction independent + // from raft. + f->layer_id = this->id() - first_object_layer_id; f->z = this->print_z; f->angle = surface_fill.params.angle; f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree; @@ -838,7 +842,11 @@ void Layer::make_ironing() FillRectilinear fill; FillParams fill_params; fill.set_bounding_box(this->object()->bounding_box()); - fill.layer_id = this->id(); + // Layer ID is used for orienting the infill in alternating directions. + // Layer::id() returns layer ID including raft layers, subtract them to make the infill direction independent + // from raft. + //FIXME ironing does not take fill angle into account. Shall it? Does it matter? + fill.layer_id = this->id() - this->object()->get_layer(0)->id(); fill.z = this->print_z; fill.overlap = 0; fill_params.density = 1.; diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 7088a158d8..28c331313b 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1,3 +1,4 @@ +#include "Config.hpp" #include "libslic3r.h" #include "GCode/ExtrusionProcessor.hpp" #include "I18N.hpp" @@ -24,6 +25,7 @@ #include #include #include +#include #include #include @@ -2867,16 +2869,36 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de ); } - bool variable_speed = false; + bool variable_speed_or_fan_speed = false; std::vector new_points{}; - if (this->m_config.enable_dynamic_overhang_speeds && !this->on_first_layer() && path.role().is_perimeter()) { + if ((this->m_config.enable_dynamic_overhang_speeds || this->config().enable_dynamic_fan_speeds.get_at(m_writer.extruder()->id())) && + !this->on_first_layer() && path.role().is_perimeter()) { + std::vector> overhangs_with_speeds = {{100, ConfigOptionFloatOrPercent{speed, false}}}; + if (this->m_config.enable_dynamic_overhang_speeds) { + overhangs_with_speeds = {{0, m_config.overhang_speed_0}, + {25, m_config.overhang_speed_1}, + {50, m_config.overhang_speed_2}, + {75, m_config.overhang_speed_3}, + {100, ConfigOptionFloatOrPercent{speed, false}}}; + } + + std::vector> overhang_w_fan_speeds = {{100, ConfigOptionInts{0}}}; + if (this->m_config.enable_dynamic_fan_speeds.get_at(m_writer.extruder()->id())) { + overhang_w_fan_speeds = {{0, m_config.overhang_fan_speed_0}, + {25, m_config.overhang_fan_speed_1}, + {50, m_config.overhang_fan_speed_2}, + {75, m_config.overhang_fan_speed_3}, + {100, ConfigOptionInts{0}}}; + } + double external_perim_reference_speed = std::min(m_config.get_abs_value("external_perimeter_speed"), std::min(EXTRUDER_CONFIG(filament_max_volumetric_speed) / path.mm3_per_mm, m_config.max_volumetric_speed.value / path.mm3_per_mm)); - new_points = m_extrusion_quality_estimator.estimate_extrusion_quality(path, m_config.overhang_overlap_levels, - m_config.dynamic_overhang_speeds, - external_perim_reference_speed, speed); - variable_speed = std::any_of(new_points.begin(), new_points.end(), [speed](const ProcessedPoint &p) { return p.speed != speed; }); + new_points = m_extrusion_quality_estimator.estimate_extrusion_quality(path, overhangs_with_speeds, overhang_w_fan_speeds, + m_writer.extruder()->id(), external_perim_reference_speed, + speed); + variable_speed_or_fan_speed = std::any_of(new_points.begin(), new_points.end(), + [speed](const ProcessedPoint &p) { return p.speed != speed || p.fan_speed != 0; }); } double F = speed * 60; // convert mm/sec to mm/min @@ -2932,7 +2954,8 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de std::string cooling_marker_setspeed_comments; if (m_enable_cooling_markers) { - if (path.role().is_bridge()) + if (path.role().is_bridge() && + (!path.role().is_perimeter() || !this->config().enable_dynamic_fan_speeds.get_at(m_writer.extruder()->id()))) gcode += ";_BRIDGE_FAN_START\n"; else cooling_marker_setspeed_comments = ";_EXTRUDE_SET_SPEED"; @@ -2940,7 +2963,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de cooling_marker_setspeed_comments += ";_EXTERNAL_PERIMETER"; } - if (!variable_speed) { + if (!variable_speed_or_fan_speed) { // F is mm per minute. gcode += m_writer.set_speed(F, "", cooling_marker_setspeed_comments); double path_length = 0.; @@ -2965,21 +2988,28 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de marked_comment = description; marked_comment += description_bridge; } - double last_set_speed = new_points[0].speed * 60.0; + double last_set_speed = new_points[0].speed * 60.0; + double last_set_fan_speed = new_points[0].fan_speed; gcode += m_writer.set_speed(last_set_speed, "", cooling_marker_setspeed_comments); + gcode += ";_SET_FAN_SPEED" + std::to_string(int(last_set_fan_speed)) + "\n"; Vec2d prev = this->point_to_gcode_quantized(new_points[0].p); for (size_t i = 1; i < new_points.size(); i++) { - const ProcessedPoint& processed_point = new_points[i]; - Vec2d p = this->point_to_gcode_quantized(processed_point.p); - const double line_length = (p - prev).norm(); + const ProcessedPoint &processed_point = new_points[i]; + Vec2d p = this->point_to_gcode_quantized(processed_point.p); + const double line_length = (p - prev).norm(); gcode += m_writer.extrude_to_xy(p, e_per_mm * line_length, marked_comment); - prev = p; + prev = p; double new_speed = processed_point.speed * 60.0; if (last_set_speed != new_speed) { gcode += m_writer.set_speed(new_speed, "", cooling_marker_setspeed_comments); last_set_speed = new_speed; } + if (last_set_fan_speed != processed_point.fan_speed) { + last_set_fan_speed = processed_point.fan_speed; + gcode += ";_SET_FAN_SPEED" + std::to_string(int(last_set_fan_speed)) + "\n"; + } } + gcode += ";_RESET_FAN_SPEED\n"; } if (m_enable_cooling_markers) diff --git a/src/libslic3r/GCode/CoolingBuffer.cpp b/src/libslic3r/GCode/CoolingBuffer.cpp index f5e2d0011d..b8e37be84b 100644 --- a/src/libslic3r/GCode/CoolingBuffer.cpp +++ b/src/libslic3r/GCode/CoolingBuffer.cpp @@ -1,5 +1,6 @@ #include "../GCode.hpp" #include "CoolingBuffer.hpp" +#include #include #include #include @@ -59,6 +60,9 @@ struct CoolingLine // Would be TYPE_ADJUSTABLE, but the block of G-code lines has zero extrusion length, thus the block // cannot have its speed adjusted. This should not happen (sic!). TYPE_ADJUSTABLE_EMPTY = 1 << 12, + // Custom fan speed (introduced for overhang fan speed) + TYPE_SET_FAN_SPEED = 1 << 13, + TYPE_RESET_FAN_SPEED = 1 << 14, }; CoolingLine(unsigned int type, size_t line_start, size_t line_end) : @@ -88,6 +92,8 @@ struct CoolingLine float time; // Maximum duration of this segment. float time_max; + // Requested fan speed + int fan_speed; // If marked with the "slowdown" flag, the line has been slowed down. bool slowdown; }; @@ -499,7 +505,18 @@ std::vector CoolingBuffer::parse_layer_gcode(const std:: } else line.time = 0; line.time_max = line.time; + } else if (boost::contains(sline, ";_SET_FAN_SPEED")) { + auto speed_start = sline.find_last_of('D'); + int speed = 0; + for (char num : sline.substr(speed_start + 1)) { + speed = speed * 10 + (num - '0'); + } + line.type = CoolingLine::TYPE_SET_FAN_SPEED; + line.fan_speed = speed; + } else if (boost::contains(sline, ";_RESET_FAN_SPEED")) { + line.type = CoolingLine::TYPE_RESET_FAN_SPEED; } + if (line.type != 0) adjustment->lines.emplace_back(std::move(line)); } @@ -732,10 +749,11 @@ std::string CoolingBuffer::apply_layer_cooldown( new_gcode.reserve(gcode.size() * 2); bool bridge_fan_control = false; int bridge_fan_speed = 0; - auto change_extruder_set_fan = [ this, layer_id, layer_time, &new_gcode, &bridge_fan_control, &bridge_fan_speed ]() { + auto change_extruder_set_fan = [this, layer_id, layer_time, &new_gcode, &bridge_fan_control, &bridge_fan_speed]() { #define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_current_extruder) int min_fan_speed = EXTRUDER_CONFIG(min_fan_speed); int fan_speed_new = EXTRUDER_CONFIG(fan_always_on) ? min_fan_speed : 0; + std::pair custom_fan_speed_limits{fan_speed_new, 100 }; int disable_fan_first_layers = EXTRUDER_CONFIG(disable_fan_first_layers); // Is the fan speed ramp enabled? int full_fan_speed_layer = EXTRUDER_CONFIG(full_fan_speed_layer); @@ -752,11 +770,13 @@ std::string CoolingBuffer::apply_layer_cooldown( if (layer_time < slowdown_below_layer_time) { // Layer time very short. Enable the fan to a full throttle. fan_speed_new = max_fan_speed; + custom_fan_speed_limits.first = fan_speed_new; } else if (layer_time < fan_below_layer_time) { // Layer time quite short. Enable the fan proportionally according to the current layer time. assert(layer_time >= slowdown_below_layer_time); double t = (layer_time - slowdown_below_layer_time) / (fan_below_layer_time - slowdown_below_layer_time); fan_speed_new = int(floor(t * min_fan_speed + (1. - t) * max_fan_speed) + 0.5); + custom_fan_speed_limits.first = fan_speed_new; } } bridge_fan_speed = EXTRUDER_CONFIG(bridge_fan_speed); @@ -765,6 +785,7 @@ std::string CoolingBuffer::apply_layer_cooldown( float factor = float(int(layer_id + 1) - disable_fan_first_layers) / float(full_fan_speed_layer - disable_fan_first_layers); fan_speed_new = std::clamp(int(float(fan_speed_new) * factor + 0.5f), 0, 255); bridge_fan_speed = std::clamp(int(float(bridge_fan_speed) * factor + 0.5f), 0, 255); + custom_fan_speed_limits.second = fan_speed_new; } #undef EXTRUDER_CONFIG bridge_fan_control = bridge_fan_speed > fan_speed_new; @@ -777,11 +798,12 @@ std::string CoolingBuffer::apply_layer_cooldown( m_fan_speed = fan_speed_new; new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, m_fan_speed); } + return custom_fan_speed_limits; }; const char *pos = gcode.c_str(); int current_feedrate = 0; - change_extruder_set_fan(); + std::pair fan_speed_limits = change_extruder_set_fan(); for (const CoolingLine *line : lines) { const char *line_start = gcode.c_str() + line->line_start; const char *line_end = gcode.c_str() + line->line_end; @@ -792,9 +814,17 @@ std::string CoolingBuffer::apply_layer_cooldown( auto res = std::from_chars(line_start + m_toolchange_prefix.size(), line_end, new_extruder); if (res.ec != std::errc::invalid_argument && new_extruder != m_current_extruder) { m_current_extruder = new_extruder; - change_extruder_set_fan(); + fan_speed_limits = change_extruder_set_fan(); } new_gcode.append(line_start, line_end - line_start); + } else if (line->type & CoolingLine::TYPE_SET_FAN_SPEED) { + int new_speed = std::clamp(line->fan_speed, fan_speed_limits.first, fan_speed_limits.second); + if (m_fan_speed != new_speed) { + new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, new_speed); + m_fan_speed = new_speed; + } + } else if (line->type & CoolingLine::TYPE_RESET_FAN_SPEED){ + fan_speed_limits = change_extruder_set_fan(); } else if (line->type & CoolingLine::TYPE_BRIDGE_FAN_START) { if (bridge_fan_control) new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, bridge_fan_speed); diff --git a/src/libslic3r/GCode/ExtrusionProcessor.hpp b/src/libslic3r/GCode/ExtrusionProcessor.hpp index 7ce7a77f69..1b17488275 100644 --- a/src/libslic3r/GCode/ExtrusionProcessor.hpp +++ b/src/libslic3r/GCode/ExtrusionProcessor.hpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -238,6 +239,7 @@ struct ProcessedPoint { Point p; float speed = 1.0f; + int fan_speed = 0; }; class ExtrusionQualityEstimator @@ -257,34 +259,27 @@ public: next_layer_boundaries[object] = AABBTreeLines::LinesDistancer{to_unscaled_linesf(layer->lslices)}; } - std::vector estimate_extrusion_quality(const ExtrusionPath &path, - const ConfigOptionPercents &overlaps, - const ConfigOptionFloatsOrPercents &speeds, - float ext_perimeter_speed, - float original_speed) + std::vector estimate_extrusion_quality(const ExtrusionPath &path, + const std::vector> overhangs_w_speeds, + const std::vector> overhangs_w_fan_speeds, + size_t extruder_id, + float ext_perimeter_speed, + float original_speed) { - size_t speed_sections_count = std::min(overlaps.values.size(), speeds.values.size()); - float speed_base = ext_perimeter_speed > 0 ? ext_perimeter_speed : original_speed; - std::vector> speed_sections; - for (size_t i = 0; i < speed_sections_count; i++) { - float distance = path.width * (1.0 - (overlaps.get_at(i) / 100.0)); - float speed = speeds.get_at(i).percent ? (speed_base * speeds.get_at(i).value / 100.0) : speeds.get_at(i).value; - speed_sections.push_back({distance, speed}); + float speed_base = ext_perimeter_speed > 0 ? ext_perimeter_speed : original_speed; + std::map speed_sections; + for (size_t i = 0; i < overhangs_w_speeds.size(); i++) { + float distance = path.width * (1.0 - (overhangs_w_speeds[i].first / 100.0)); + float speed = overhangs_w_speeds[i].second.percent ? (speed_base * overhangs_w_speeds[i].second.value / 100.0) : + overhangs_w_speeds[i].second.value; + speed_sections[distance] = speed; } - std::sort(speed_sections.begin(), speed_sections.end(), - [](const std::pair &a, const std::pair &b) { - if (a.first == b.first) { - return a.second > b.second; - } - return a.first < b.first; }); - std::pair last_section{INFINITY, 0}; - for (auto §ion : speed_sections) { - if (section.first == last_section.first) { - section.second = last_section.second; - } else { - last_section = section; - } + std::map fan_speed_sections; + for (size_t i = 0; i < overhangs_w_fan_speeds.size(); i++) { + float distance = path.width * (1.0 - (overhangs_w_fan_speeds[i].first / 100.0)); + float fan_speed = overhangs_w_fan_speeds[i].second.get_at(extruder_id); + fan_speed_sections[distance] = fan_speed; } std::vector extended_points = @@ -296,28 +291,26 @@ public: const ExtendedPoint &curr = extended_points[i]; const ExtendedPoint &next = extended_points[i + 1 < extended_points.size() ? i + 1 : i]; - auto calculate_speed = [&speed_sections, &original_speed](float distance) { - float final_speed; - if (distance <= speed_sections.front().first) { - final_speed = original_speed; - } else if (distance >= speed_sections.back().first) { - final_speed = speed_sections.back().second; - } else { - size_t section_idx = 0; - while (distance > speed_sections[section_idx + 1].first) { - section_idx++; - } - float t = (distance - speed_sections[section_idx].first) / - (speed_sections[section_idx + 1].first - speed_sections[section_idx].first); - t = std::clamp(t, 0.0f, 1.0f); - final_speed = (1.0f - t) * speed_sections[section_idx].second + t * speed_sections[section_idx + 1].second; + auto interpolate_speed = [](const std::map &values, float distance) { + auto upper_dist = values.lower_bound(distance); + if (upper_dist == values.end()) { + return values.rbegin()->second; } - return final_speed; + if (upper_dist == values.begin()) { + return upper_dist->second; + } + + auto lower_dist = std::prev(upper_dist); + float t = (distance - lower_dist->first) / (upper_dist->first - lower_dist->first); + return (1.0f - t) * lower_dist->second + t * upper_dist->second; }; - float extrusion_speed = std::min(calculate_speed(curr.distance), calculate_speed(next.distance)); + float extrusion_speed = std::min(interpolate_speed(speed_sections, curr.distance), + interpolate_speed(speed_sections, next.distance)); + float fan_speed = std::min(interpolate_speed(fan_speed_sections, curr.distance), + interpolate_speed(fan_speed_sections, next.distance)); - processed_points.push_back({scaled(curr.position), extrusion_speed}); + processed_points.push_back({scaled(curr.position), extrusion_speed, int(fan_speed)}); } return processed_points; } diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index a2a93614e3..9fa3c74304 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -440,7 +440,7 @@ static std::vector s_Preset_print_options { "fuzzy_skin", "fuzzy_skin_thickness", "fuzzy_skin_point_dist", "max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative", "perimeter_speed", "small_perimeter_speed", "external_perimeter_speed", "infill_speed", "solid_infill_speed", - "enable_dynamic_overhang_speeds", "dynamic_overhang_speeds", "overhang_overlap_levels", + "enable_dynamic_overhang_speeds", "overhang_speed_0", "overhang_speed_1", "overhang_speed_2", "overhang_speed_3", "top_solid_infill_speed", "support_material_speed", "support_material_xy_spacing", "support_material_interface_speed", "bridge_speed", "gap_fill_speed", "gap_fill_enabled", "travel_speed", "travel_speed_z", "first_layer_speed", "first_layer_speed_over_raft", "perimeter_acceleration", "infill_acceleration", "external_perimeter_acceleration", "top_solid_infill_acceleration", "solid_infill_acceleration", @@ -473,7 +473,8 @@ static std::vector s_Preset_filament_options { "filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters", "filament_minimal_purge_on_wipe_tower", "temperature", "idle_temperature", "first_layer_temperature", "bed_temperature", "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed", "max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers", "full_fan_speed_layer", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed", - "start_filament_gcode", "end_filament_gcode", + "start_filament_gcode", "end_filament_gcode", "enable_dynamic_fan_speeds", + "overhang_fan_speed_0", "overhang_fan_speed_1", "overhang_fan_speed_2", "overhang_fan_speed_3", // Retract overrides "filament_retract_length", "filament_retract_lift", "filament_retract_lift_above", "filament_retract_lift_below", "filament_retract_speed", "filament_deretract_speed", "filament_retract_restart_extra", "filament_retract_before_travel", "filament_retract_layer_change", "filament_wipe", "filament_retract_before_wipe", diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 3c810b178e..8d3ad0066d 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -66,6 +66,11 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n "between_objects_gcode", "bridge_acceleration", "bridge_fan_speed", + "enable_dynamic_fan_speeds", + "overhang_fan_speed_0", + "overhang_fan_speed_1", + "overhang_fan_speed_2", + "overhang_fan_speed_3", "colorprint_heights", "cooling", "default_acceleration", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 76fddebc4d..3425cfd7a0 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -535,35 +535,96 @@ void PrintConfigDef::init_fff_params() def->label = L("Enable dynamic overhang speeds"); def->category = L("Speed"); def->tooltip = L("This setting enables dynamic speed control on overhangs."); - def->mode = comAdvanced; + def->mode = comExpert; def->set_default_value(new ConfigOptionBool(false)); - def = this->add("overhang_overlap_levels", coPercents); - def->full_label = L("Overhang overlap levels"); - def->category = L("Speed"); - def->tooltip = L("Controls overhang levels, expressed as a percentage of overlap of the extrusion with the previous layer - " - "100% represents full overlap - no overhang is present, while 0% represents full overhang (floating extrusion). " - "Each overhang level then corresponds with the overhang speed below. Speeds for overhang levels in between are " - "calculated via linear interpolation." - "If you set multiple different speeds for the same overhang level, only the largest speed is used. " - ); - def->sidetext = L("%"); - def->min = 0; - def->max = 100; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionPercents({60, 40, 20, 0})); + auto overhang_speed_setting_description = L("Overhang size is expressed as a percentage of overlap of the extrusion with the previous layer: " + "100% would be full overlap (no overhang), while 0% represents full overhang (floating extrusion, bridge). " + "Speeds for overhang sizes in between are calculated via linear interpolation. " + "If set as percentage, the speed is calculated over the external perimeter speed."); - def = this->add("dynamic_overhang_speeds", coFloatsOrPercents); - def->full_label = L("Dynamic speed on overhangs"); + def = this->add("overhang_speed_0", coFloatOrPercent); + def->label = L("speed for 0\% overlap (bridge)"); def->category = L("Speed"); - def->tooltip = L("This setting controls the speed on the overhang with the overlap value set above. " - "The speed of the extrusion is calculated as a linear interpolation of the speeds for higher and lower overlap. " - "If set as percentage, the speed is calculated over the external perimeter speed." - ); + def->tooltip = overhang_speed_setting_description; def->sidetext = L("mm/s or %"); def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloatsOrPercents({{25, false}, {20, false}, {15, false}, {15, false}})); + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloatOrPercent(15, false)); + + def = this->add("overhang_speed_1", coFloatOrPercent); + def->label = L("speed for 25\% overlap"); + def->category = L("Speed"); + def->tooltip = overhang_speed_setting_description; + def->sidetext = L("mm/s or %"); + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloatOrPercent(15, false)); + + def = this->add("overhang_speed_2", coFloatOrPercent); + def->label = L("speed for 50\% overlap"); + def->category = L("Speed"); + def->tooltip = overhang_speed_setting_description; + def->sidetext = L("mm/s or %"); + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloatOrPercent(20, false)); + + def = this->add("overhang_speed_3", coFloatOrPercent); + def->label = L("speed for 75\% overlap"); + def->category = L("Speed"); + def->tooltip = overhang_speed_setting_description; + def->sidetext = L("mm/s or %"); + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloatOrPercent(25, false)); + + def = this->add("enable_dynamic_fan_speeds", coBools); + def->label = L("Enable dynamic fan speeds"); + def->tooltip = L("This setting enables dynamic fan speed control on overhangs."); + def->mode = comExpert; + def->set_default_value(new ConfigOptionBools{false}); + + auto fan_speed_setting_description = L( + "Overhang size is expressed as a percentage of overlap of the extrusion with the previous layer: " + "100% would be full overlap (no overhang), while 0% represents full overhang (floating extrusion, bridge). " + "Fan speeds for overhang sizes in between are calculated via linear interpolation. "); + + def = this->add("overhang_fan_speed_0", coInts); + def->label = L("speed for 0\% overlap (bridge)"); + def->tooltip = fan_speed_setting_description; + def->sidetext = L("%"); + def->min = 0; + def->max = 100; + def->mode = comExpert; + def->set_default_value(new ConfigOptionInts{0}); + + def = this->add("overhang_fan_speed_1", coInts); + def->label = L("speed for 25\% overlap"); + def->tooltip = fan_speed_setting_description; + def->sidetext = L("%"); + def->min = 0; + def->max = 100; + def->mode = comExpert; + def->set_default_value(new ConfigOptionInts{0}); + + def = this->add("overhang_fan_speed_2", coInts); + def->label = L("speed for 50\% overlap"); + def->tooltip = fan_speed_setting_description; + def->sidetext = L("%"); + def->min = 0; + def->max = 100; + def->mode = comExpert; + def->set_default_value(new ConfigOptionInts{0}); + + def = this->add("overhang_fan_speed_3", coInts); + def->label = L("speed for 75\% overlap"); + def->tooltip = fan_speed_setting_description; + def->sidetext = L("%"); + def->min = 0; + def->max = 100; + def->mode = comExpert; + def->set_default_value(new ConfigOptionInts{0}); def = this->add("brim_width", coFloat); def->label = L("Brim width"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index cfd852ca14..df57ab1da2 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -575,8 +575,10 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloatOrPercent, external_perimeter_extrusion_width)) ((ConfigOptionFloatOrPercent, external_perimeter_speed)) ((ConfigOptionBool, enable_dynamic_overhang_speeds)) - ((ConfigOptionPercents, overhang_overlap_levels)) - ((ConfigOptionFloatsOrPercents, dynamic_overhang_speeds)) + ((ConfigOptionFloatOrPercent, overhang_speed_0)) + ((ConfigOptionFloatOrPercent, overhang_speed_1)) + ((ConfigOptionFloatOrPercent, overhang_speed_2)) + ((ConfigOptionFloatOrPercent, overhang_speed_3)) ((ConfigOptionBool, external_perimeters_first)) ((ConfigOptionBool, extra_perimeters)) ((ConfigOptionBool, extra_perimeters_on_overhangs)) @@ -749,6 +751,11 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionInts, bed_temperature)) ((ConfigOptionFloat, bridge_acceleration)) ((ConfigOptionInts, bridge_fan_speed)) + ((ConfigOptionBools, enable_dynamic_fan_speeds)) + ((ConfigOptionInts, overhang_fan_speed_0)) + ((ConfigOptionInts, overhang_fan_speed_1)) + ((ConfigOptionInts, overhang_fan_speed_2)) + ((ConfigOptionInts, overhang_fan_speed_3)) ((ConfigOptionBool, complete_objects)) ((ConfigOptionFloats, colorprint_heights)) ((ConfigOptionBools, cooling)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 0c20acc179..1511b72cd7 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -26,6 +26,7 @@ #include "Fill/FillAdaptive.hpp" #include "Fill/FillLightning.hpp" #include "Format/STL.hpp" +#include "SupportMaterial.hpp" #include "SupportSpotsGenerator.hpp" #include "TriangleSelectorWrapper.hpp" #include "format.hpp" @@ -784,8 +785,10 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "support_material_interface_speed" || opt_key == "bridge_speed" || opt_key == "enable_dynamic_overhang_speeds" - || opt_key == "overhang_overlap_levels" - || opt_key == "dynamic_overhang_speeds" + || opt_key == "overhang_speed_0" + || opt_key == "overhang_speed_1" + || opt_key == "overhang_speed_2" + || opt_key == "overhang_speed_3" || opt_key == "external_perimeter_speed" || opt_key == "infill_speed" || opt_key == "perimeter_speed" @@ -1152,6 +1155,15 @@ void PrintObject::process_external_surfaces() m_print->throw_if_canceled(); BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces for region " << region_id << " in parallel - end"; } + + if (this->has_raft() && ! m_layers.empty()) { + // Adjust bridge direction of 1st object layer over raft to be perpendicular to the raft contact layer direction. + Layer &layer = *m_layers.front(); + assert(layer.id() > 0); + for (LayerRegion *layerm : layer.regions()) + for (Surface &fill : layerm->m_fill_surfaces) + fill.bridge_angle = -1; + } } // void PrintObject::process_external_surfaces() void PrintObject::discover_vertical_shells() diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 1c61a1eb02..224216466d 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -376,8 +376,6 @@ SupportParameters::SupportParameters(const PrintObject &object) } - this->base_angle = Geometry::deg2rad(float(object_config.support_material_angle.value)); - this->interface_angle = Geometry::deg2rad(float(object_config.support_material_angle.value + 90.)); double interface_spacing = object_config.support_material_interface_spacing.value + this->support_material_interface_flow.spacing(); this->interface_density = std::min(1., this->support_material_interface_flow.spacing() / interface_spacing); double raft_interface_spacing = object_config.support_material_interface_spacing.value + this->raft_interface_flow.spacing(); @@ -401,6 +399,39 @@ SupportParameters::SupportParameters(const PrintObject &object) object_config.support_material_interface_pattern == smipConcentric ? ipConcentric : (this->interface_density > 0.95 ? ipRectilinear : ipSupportBase); + + this->base_angle = Geometry::deg2rad(float(object_config.support_material_angle.value)); + this->interface_angle = Geometry::deg2rad(float(object_config.support_material_angle.value + 90.)); + this->raft_angle_1st_layer = 0.f; + this->raft_angle_base = 0.f; + this->raft_angle_interface = 0.f; + if (slicing_params.base_raft_layers > 1) { + assert(slicing_params.raft_layers() >= 4); + // There are all raft layer types (1st layer, base, interface & contact layers) available. + this->raft_angle_1st_layer = this->interface_angle; + this->raft_angle_base = this->base_angle; + this->raft_angle_interface = this->interface_angle; + if ((slicing_params.interface_raft_layers & 1) == 0) + // Allign the 1st raft interface layer so that the object 1st layer is hatched perpendicularly to the raft contact interface. + this->raft_angle_interface += float(0.5 * M_PI); + } else if (slicing_params.base_raft_layers == 1 || slicing_params.interface_raft_layers > 1) { + assert(slicing_params.raft_layers() == 2 || slicing_params.raft_layers() == 3); + // 1st layer, interface & contact layers available. + this->raft_angle_1st_layer = this->base_angle; + this->raft_angle_interface = this->interface_angle + 0.5 * M_PI; + } else if (slicing_params.interface_raft_layers == 1) { + // Only the contact raft layer is non-empty, which will be printed as the 1st layer. + assert(slicing_params.base_raft_layers == 0); + assert(slicing_params.interface_raft_layers == 1); + assert(slicing_params.raft_layers() == 1); + this->raft_angle_1st_layer = float(0.5 * M_PI); + this->raft_angle_interface = this->raft_angle_1st_layer; + } else { + // No raft. + assert(slicing_params.base_raft_layers == 0); + assert(slicing_params.interface_raft_layers == 0); + assert(slicing_params.raft_layers() == 0); + } } PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object, const SlicingParameters &slicing_params) : @@ -4207,38 +4238,12 @@ void generate_support_toolpaths( // const coordf_t link_max_length_factor = 3.; const coordf_t link_max_length_factor = 0.; - float raft_angle_1st_layer = 0.f; - float raft_angle_base = 0.f; - float raft_angle_interface = 0.f; - if (slicing_params.base_raft_layers > 1) { - // There are all raft layer types (1st layer, base, interface & contact layers) available. - raft_angle_1st_layer = support_params.interface_angle; - raft_angle_base = support_params.base_angle; - raft_angle_interface = support_params.interface_angle; - } else if (slicing_params.base_raft_layers == 1 || slicing_params.interface_raft_layers > 1) { - // 1st layer, interface & contact layers available. - raft_angle_1st_layer = support_params.base_angle; - if (config.support_material || config.support_material_enforce_layers > 0) - // Print 1st layer at 45 degrees from both the interface and base angles as both can land on the 1st layer. - raft_angle_1st_layer += 0.7854f; - raft_angle_interface = support_params.interface_angle; - } else if (slicing_params.interface_raft_layers == 1) { - // Only the contact raft layer is non-empty, which will be printed as the 1st layer. - assert(slicing_params.base_raft_layers == 0); - assert(slicing_params.interface_raft_layers == 1); - assert(slicing_params.raft_layers() == 1 && raft_layers.size() == 0); - } else { - // No raft. - assert(slicing_params.base_raft_layers == 0); - assert(slicing_params.interface_raft_layers == 0); - assert(slicing_params.raft_layers() == 0 && raft_layers.size() == 0); - } - // Insert the raft base layers. auto n_raft_layers = std::min(support_layers.size(), std::max(0, int(slicing_params.raft_layers()) - 1)); + tbb::parallel_for(tbb::blocked_range(0, n_raft_layers), [&support_layers, &raft_layers, &intermediate_layers, &config, &support_params, &slicing_params, - &bbox_object, raft_angle_1st_layer, raft_angle_base, raft_angle_interface, link_max_length_factor] + &bbox_object, link_max_length_factor] (const tbb::blocked_range& range) { for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) { @@ -4270,7 +4275,7 @@ void generate_support_toolpaths( assert(!raft_layer.bridging); if (! to_infill_polygons.empty()) { Fill *filler = filler_support.get(); - filler->angle = raft_angle_base; + filler->angle = support_params.raft_angle_base; filler->spacing = support_params.support_material_flow.spacing(); filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.support_density)); fill_expolygons_with_sheath_generate_paths( @@ -4293,11 +4298,11 @@ void generate_support_toolpaths( float density = 0.f; if (support_layer_id == 0) { // Base flange. - filler->angle = raft_angle_1st_layer; + filler->angle = support_params.raft_angle_1st_layer; filler->spacing = support_params.first_layer_flow.spacing(); density = float(config.raft_first_layer_density.value * 0.01); } else if (support_layer_id >= slicing_params.base_raft_layers) { - filler->angle = raft_angle_interface; + filler->angle = support_params.raft_interface_angle(support_layer.interface_id()); // We don't use $base_flow->spacing because we need a constant spacing // value that guarantees that all layers are correctly aligned. filler->spacing = support_params.support_material_flow.spacing(); @@ -4345,7 +4350,7 @@ void generate_support_toolpaths( std::vector layer_caches(support_layers.size()); tbb::parallel_for(tbb::blocked_range(n_raft_layers, support_layers.size()), - [&config, &support_params, &support_layers, &bottom_contacts, &top_contacts, &intermediate_layers, &interface_layers, &base_interface_layers, &layer_caches, &loop_interface_processor, + [&config, &slicing_params, &support_params, &support_layers, &bottom_contacts, &top_contacts, &intermediate_layers, &interface_layers, &base_interface_layers, &layer_caches, &loop_interface_processor, &bbox_object, &angles, n_raft_layers, link_max_length_factor] (const tbb::blocked_range& range) { // Indices of the 1st layer in their respective container at the support layer height. @@ -4381,9 +4386,8 @@ void generate_support_toolpaths( { SupportLayer &support_layer = *support_layers[support_layer_id]; LayerCache &layer_cache = layer_caches[support_layer_id]; - float interface_angle_delta = config.support_material_style.value != smsGrid ? - (support_layer.interface_id() & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.) : - 0; + const float support_interface_angle = config.support_material_style.value == smsGrid ? + support_params.interface_angle : support_params.raft_interface_angle(support_layer.interface_id()); // Find polygons with the same print_z. SupportGeneratorLayerExtruded &bottom_contact_layer = layer_cache.bottom_contact_layer; @@ -4412,7 +4416,8 @@ void generate_support_toolpaths( if (idx_layer_intermediate < intermediate_layers.size() && intermediate_layers[idx_layer_intermediate]->print_z < support_layer.print_z + EPSILON) base_layer.layer = intermediate_layers[idx_layer_intermediate]; - bool raft_layer = support_layer_id == n_raft_layers; + // This layer is a raft contact layer. Any contact polygons at this layer are raft contacts. + bool raft_layer = slicing_params.interface_raft_layers && top_contact_layer.layer && is_approx(top_contact_layer.layer->print_z, slicing_params.raft_contact_top_z); if (config.support_material_interface_layers == 0) { // If no top interface layers were requested, we treat the contact layer exactly as a generic base layer. // Don't merge the raft contact layer though. @@ -4470,7 +4475,9 @@ void generate_support_toolpaths( // If zero interface layers are configured, use the same angle as for the base layers. angles[support_layer_id % angles.size()] : // Use interface angle for the interface layers. - support_params.interface_angle + interface_angle_delta; + raft_contact ? + support_params.raft_interface_angle(support_layer.interface_id()) : + support_interface_angle; double density = raft_contact ? support_params.raft_interface_density : interface_as_base ? support_params.support_density : support_params.interface_density; filler->spacing = raft_contact ? support_params.raft_interface_flow.spacing() : interface_as_base ? support_params.support_material_flow.spacing() : support_params.support_material_interface_flow.spacing(); @@ -4499,7 +4506,7 @@ void generate_support_toolpaths( // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) assert(! base_interface_layer.layer->bridging); Flow interface_flow = support_params.support_material_flow.with_height(float(base_interface_layer.layer->height)); - filler->angle = support_params.interface_angle + interface_angle_delta; + filler->angle = support_interface_angle; filler->spacing = support_params.support_material_interface_flow.spacing(); filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.interface_density)); fill_expolygons_generate_paths( diff --git a/src/libslic3r/SupportMaterial.hpp b/src/libslic3r/SupportMaterial.hpp index eda70517f3..2bd3211445 100644 --- a/src/libslic3r/SupportMaterial.hpp +++ b/src/libslic3r/SupportMaterial.hpp @@ -161,6 +161,14 @@ struct SupportParameters { InfillPattern contact_fill_pattern; // Shall the sparse (base) layers be printed with a single perimeter line (sheath) for robustness? bool with_sheath; + + float raft_angle_1st_layer; + float raft_angle_base; + float raft_angle_interface; + + // Produce a raft interface angle for a given SupportLayer::interface_id() + float raft_interface_angle(size_t interface_id) const + { return this->raft_angle_interface + ((interface_id & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.)); } }; // Remove bridges from support contact areas. diff --git a/src/libslic3r/TreeModelVolumes.cpp b/src/libslic3r/TreeModelVolumes.cpp index 53ac6534dd..33758f3f0a 100644 --- a/src/libslic3r/TreeModelVolumes.cpp +++ b/src/libslic3r/TreeModelVolumes.cpp @@ -491,9 +491,8 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex if (const std::vector &outlines = m_layer_outlines[outline_idx].second; ! outlines.empty()) { const TreeSupportMeshGroupSettings &settings = m_layer_outlines[outline_idx].first; const coord_t layer_height = settings.layer_height; - const coord_t z_distance_bottom = settings.support_bottom_distance; - const int z_distance_bottom_layers = round_up_divide(z_distance_bottom, layer_height); - const int z_distance_top_layers = round_up_divide(settings.support_top_distance, layer_height); + const int z_distance_bottom_layers = int(round(double(settings.support_bottom_distance) / double(layer_height))); + const int z_distance_top_layers = int(round(double(settings.support_top_distance) / double(layer_height))); const LayerIndex max_required_layer = std::min(outlines.size(), max_layer_idx + std::max(coord_t(1), z_distance_top_layers)); const LayerIndex min_layer_bottom = std::max(0, min_layer_last - int(z_distance_bottom_layers)); const coord_t xy_distance = outline_idx == m_current_outline_idx ? m_current_min_xy_dist : diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index 8dc48ee091..2906f6b93e 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -80,8 +80,8 @@ TreeSupportSettings::TreeSupportSettings(const TreeSupportMeshGroupSettings& mes xy_min_distance(std::min(mesh_group_settings.support_xy_distance, mesh_group_settings.support_xy_distance_overhang)), bp_radius(mesh_group_settings.support_tree_bp_diameter / 2), diameter_scale_bp_radius(std::min(sin(0.7) * layer_height / branch_radius, 1.0 / (branch_radius / (support_line_width / 2.0)))), // Either 40? or as much as possible so that 2 lines will overlap by at least 50%, whichever is smaller. - z_distance_top_layers(round_up_divide(mesh_group_settings.support_top_distance, layer_height)), - z_distance_bottom_layers(round_up_divide(mesh_group_settings.support_bottom_distance, layer_height)), + z_distance_bottom_layers(size_t(round(double(mesh_group_settings.support_bottom_distance) / double(layer_height)))), + z_distance_top_layers(size_t(round(double(mesh_group_settings.support_top_distance) / double(layer_height)))), performance_interface_skip_layers(round_up_divide(mesh_group_settings.support_interface_skip_height, layer_height)), // support_infill_angles(mesh_group_settings.support_infill_angles), support_roof_angles(mesh_group_settings.support_roof_angles), diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 120aeecf30..9a0cf38812 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -221,12 +221,11 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) bool have_perimeters = config->opt_int("perimeters") > 0; for (auto el : { "extra_perimeters","extra_perimeters_on_overhangs", "ensure_vertical_shell_thickness", "thin_walls", "overhangs", "seam_position","staggered_inner_seams", "external_perimeters_first", "external_perimeter_extrusion_width", - "perimeter_speed", "small_perimeter_speed", "external_perimeter_speed", "enable_dynamic_overhang_speeds", "overhang_overlap_levels", "dynamic_overhang_speeds" }) + "perimeter_speed", "small_perimeter_speed", "external_perimeter_speed", "enable_dynamic_overhang_speeds"}) toggle_field(el, have_perimeters); for (size_t i = 0; i < 4; i++) { - toggle_field("overhang_overlap_levels#" + std::to_string(i), config->opt_bool("enable_dynamic_overhang_speeds")); - toggle_field("dynamic_overhang_speeds#" + std::to_string(i), config->opt_bool("enable_dynamic_overhang_speeds")); + toggle_field("overhang_speed_" + std::to_string(i), config->opt_bool("enable_dynamic_overhang_speeds")); } bool have_infill = config->option("fill_density")->value > 0; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 17ddd63abd..ae9bb175f5 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1549,18 +1549,11 @@ void TabPrint::build() optgroup->append_single_option_line("ironing_speed"); optgroup = page->new_optgroup(L("Dynamic overhang speed")); - auto append_option_line = [](ConfigOptionsGroupShp optgroup, std::string opt_key) { - auto option = optgroup->get_option(opt_key, 0); - auto line = Line{option.opt.full_label, ""}; - line.append_option(option); - line.append_option(optgroup->get_option(opt_key, 1)); - line.append_option(optgroup->get_option(opt_key, 2)); - line.append_option(optgroup->get_option(opt_key, 3)); - optgroup->append_line(line); - }; optgroup->append_single_option_line("enable_dynamic_overhang_speeds"); - append_option_line(optgroup,"overhang_overlap_levels"); - append_option_line(optgroup,"dynamic_overhang_speeds"); + optgroup->append_single_option_line("overhang_speed_0"); + optgroup->append_single_option_line("overhang_speed_1"); + optgroup->append_single_option_line("overhang_speed_2"); + optgroup->append_single_option_line("overhang_speed_3"); optgroup = page->new_optgroup(L("Speed for non-print moves")); optgroup->append_single_option_line("travel_speed"); @@ -1994,6 +1987,13 @@ void TabFilament::build() optgroup->append_single_option_line("disable_fan_first_layers", category_path + "fan-settings"); optgroup->append_single_option_line("full_fan_speed_layer", category_path + "fan-settings"); + optgroup = page->new_optgroup(L("Dynamic fan speeds"), 25); + optgroup->append_single_option_line("enable_dynamic_fan_speeds", category_path + "dynamic-fan-speeds"); + optgroup->append_single_option_line("overhang_fan_speed_0", category_path + "dynamic-fan-speeds"); + optgroup->append_single_option_line("overhang_fan_speed_1", category_path + "dynamic-fan-speeds"); + optgroup->append_single_option_line("overhang_fan_speed_2", category_path + "dynamic-fan-speeds"); + optgroup->append_single_option_line("overhang_fan_speed_3", category_path + "dynamic-fan-speeds"); + optgroup = page->new_optgroup(L("Cooling thresholds"), 25); optgroup->append_single_option_line("fan_below_layer_time", category_path + "cooling-thresholds"); optgroup->append_single_option_line("slowdown_below_layer_time", category_path + "cooling-thresholds"); @@ -2146,6 +2146,11 @@ void TabFilament::toggle_options() for (auto el : { "min_fan_speed", "disable_fan_first_layers", "full_fan_speed_layer" }) toggle_option(el, fan_always_on); + + bool dynamic_fan_speeds = m_config->opt_bool("enable_dynamic_fan_speeds", 0); + for (int i = 0; i < 4; i++) { + toggle_option("overhang_fan_speed_"+std::to_string(i),dynamic_fan_speeds); + } } if (m_active_page->title() == "Filament Overrides")