diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 97333677e8..ecaf4a3337 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -508,13 +508,14 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: // apply half spacing using this flow's own spacing and generate infill FillParams params; - params.density = float(0.01 * surface_fill.params.density); - params.dont_adjust = false; // surface_fill.params.dont_adjust; - params.anchor_length = surface_fill.params.anchor_length; - params.anchor_length_max = surface_fill.params.anchor_length_max; - params.resolution = resolution; - params.use_arachne = (perimeter_generator == PerimeterGeneratorType::Arachne && surface_fill.params.pattern == ipConcentric) || surface_fill.params.pattern == ipEnsuring; - params.layer_height = layerm.layer()->height; + params.density = float(0.01 * surface_fill.params.density); + params.dont_adjust = false; // surface_fill.params.dont_adjust; + params.anchor_length = surface_fill.params.anchor_length; + params.anchor_length_max = surface_fill.params.anchor_length_max; + params.resolution = resolution; + params.use_arachne = (perimeter_generator == PerimeterGeneratorType::Arachne && surface_fill.params.pattern == ipConcentric) || surface_fill.params.pattern == ipEnsuring; + params.layer_height = layerm.layer()->height; + params.prefer_clockwise_movements = this->object()->print()->config().prefer_clockwise_movements; for (ExPolygon &expoly : surface_fill.expolygons) { // Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon. @@ -569,11 +570,12 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: thick_polylines.clear(); } else { + // When prefer_clockwise_movements is true, we have to ensure that extrusion paths will not be reversed during path planning. extrusion_entities_append_paths( eec->entities, std::move(polylines), ExtrusionAttributes{ surface_fill.params.extrusion_role, ExtrusionFlow{ flow_mm3_per_mm, float(flow_width), surface_fill.params.flow.height() } - }); + }, !params.prefer_clockwise_movements); layerm.m_fills.entities.push_back(eec); } insert_fills_into_islands(*this, uint32_t(surface_fill.region_id), fill_begin, uint32_t(layerm.fills().size())); diff --git a/src/libslic3r/Fill/FillBase.hpp b/src/libslic3r/Fill/FillBase.hpp index 2d96b60d6d..fd0c47a89c 100644 --- a/src/libslic3r/Fill/FillBase.hpp +++ b/src/libslic3r/Fill/FillBase.hpp @@ -72,6 +72,9 @@ struct FillParams bool use_arachne { false }; // Layer height for Concentric infill with Arachne. coordf_t layer_height { 0.f }; + + // For infills that produce closed loops to force printing those loops clockwise. + bool prefer_clockwise_movements { false }; }; static_assert(IsTriviallyCopyable::value, "FillParams class is not POD (and it should be - see constructor)."); diff --git a/src/libslic3r/Fill/FillConcentric.cpp b/src/libslic3r/Fill/FillConcentric.cpp index 91f3080ca1..43eb104a51 100644 --- a/src/libslic3r/Fill/FillConcentric.cpp +++ b/src/libslic3r/Fill/FillConcentric.cpp @@ -56,14 +56,19 @@ void FillConcentric::_fill_surface_single( // clip the paths to prevent the extruder from getting exactly on the first point of the loop // Keep valid paths only. size_t j = iPathFirst; - for (size_t i = iPathFirst; i < polylines_out.size(); ++ i) { + for (size_t i = iPathFirst; i < polylines_out.size(); ++i) { polylines_out[i].clip_end(this->loop_clipping); if (polylines_out[i].is_valid()) { + if (params.prefer_clockwise_movements) + polylines_out[i].reverse(); + if (j < i) polylines_out[j] = std::move(polylines_out[i]); - ++ j; + + ++j; } } + if (j < polylines_out.size()) polylines_out.erase(polylines_out.begin() + int(j), polylines_out.end()); //TODO: return ExtrusionLoop objects to get better chained paths, @@ -94,6 +99,7 @@ void FillConcentric::_fill_surface_single(const FillParams ¶ms, for (Arachne::VariableWidthLines &loop : loops) { if (loop.empty()) continue; + for (const Arachne::ExtrusionLine &wall : loop) all_extrusions.emplace_back(&wall); } @@ -106,8 +112,14 @@ void FillConcentric::_fill_surface_single(const FillParams ¶ms, continue; ThickPolyline thick_polyline = Arachne::to_thick_polyline(*extrusion); - if (extrusion->is_closed) + if (extrusion->is_closed) { + // Arachne produces contour with clockwise orientation and holes with counterclockwise orientation. + if (const bool extrusion_reverse = params.prefer_clockwise_movements ? !extrusion->is_contour() : extrusion->is_contour(); extrusion_reverse) + thick_polyline.reverse(); + thick_polyline.start_at_index(nearest_point_index(thick_polyline.points, last_pos)); + } + thick_polylines_out.emplace_back(std::move(thick_polyline)); last_pos = thick_polylines_out.back().last_point(); } diff --git a/src/libslic3r/Fill/FillEnsuring.cpp b/src/libslic3r/Fill/FillEnsuring.cpp index 1b68bfe5e9..9465a6f0d6 100644 --- a/src/libslic3r/Fill/FillEnsuring.cpp +++ b/src/libslic3r/Fill/FillEnsuring.cpp @@ -308,19 +308,25 @@ ThickPolylines make_fill_polylines( for (Arachne::VariableWidthLines &loop : loops) { if (loop.empty()) continue; + for (const Arachne::ExtrusionLine &wall : loop) all_extrusions.emplace_back(&wall); } for (const Arachne::ExtrusionLine *extrusion : all_extrusions) { - if (extrusion->junctions.size() < 2) { + if (extrusion->junctions.size() < 2) continue; - } + ThickPolyline thick_polyline = Arachne::to_thick_polyline(*extrusion); if (extrusion->is_closed) { + // Arachne produces contour with clockwise orientation and holes with counterclockwise orientation. + if (const bool extrusion_reverse = params.prefer_clockwise_movements ? !extrusion->is_contour() : extrusion->is_contour(); extrusion_reverse) + thick_polyline.reverse(); + thick_polyline.start_at_index(nearest_point_index(thick_polyline.points, ex_bb.min)); thick_polyline.clip_end(scaled_spacing * 0.5); } + if (thick_polyline.is_valid() && thick_polyline.length() > 0 && thick_polyline.points.size() > 1) { thick_polylines.push_back(thick_polyline); } diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 74ab5b4b44..ee223884fe 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2977,8 +2977,10 @@ static constexpr const double min_gcode_segment_length = 0.002; std::string GCodeGenerator::extrude_loop(const ExtrusionLoop &loop_src, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed) { - // Extrude all loops CCW. - bool is_hole = loop_src.is_clockwise(); + // Extrude all loops CCW unless CW movements are prefered. + const bool is_hole = loop_src.is_clockwise(); + const bool reverse_loop = m_config.prefer_clockwise_movements ? !is_hole : is_hole; + Point seam_point = this->last_position.has_value() ? *this->last_position : Point::Zero(); if (!m_config.spiral_vase && comment_is_perimeter(description)) { assert(m_layer != nullptr); @@ -2986,8 +2988,7 @@ std::string GCodeGenerator::extrude_loop(const ExtrusionLoop &loop_src, const GC } // Because the G-code export has 1um resolution, don't generate segments shorter than 1.5 microns, // thus empty path segments will not be produced by G-code export. - GCode::SmoothPath smooth_path = smooth_path_cache.resolve_or_fit_split_with_seam( - loop_src, is_hole, m_scaled_resolution, seam_point, scaled(0.0015)); + GCode::SmoothPath smooth_path = smooth_path_cache.resolve_or_fit_split_with_seam(loop_src, reverse_loop, m_scaled_resolution, seam_point, scaled(0.0015)); // Clip the path to avoid the extruder to get exactly on the first point of the loop; // if polyline was shorter than the clipping distance we'd get a null polyline, so @@ -3018,7 +3019,7 @@ std::string GCodeGenerator::extrude_loop(const ExtrusionLoop &loop_src, const GC } else if (loop_src.paths.back().role().is_external_perimeter() && m_layer != nullptr && m_config.perimeters.value > 1) { // Only wipe inside if the wipe along the perimeter is disabled. // Make a little move inwards before leaving loop. - if (std::optional pt = wipe_hide_seam(smooth_path, is_hole, scale_(EXTRUDER_CONFIG(nozzle_diameter))); pt) { + if (std::optional pt = wipe_hide_seam(smooth_path, reverse_loop, scale_(EXTRUDER_CONFIG(nozzle_diameter))); pt) { // Generate the seam hiding travel move. gcode += m_writer.travel_to_xy(this->point_to_gcode(*pt), "move inwards before travel"); this->last_position = *pt; @@ -3033,9 +3034,10 @@ std::string GCodeGenerator::extrude_skirt( const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed) { assert(loop_src.is_counter_clockwise()); + const bool reverse_loop = m_config.prefer_clockwise_movements; + Point seam_point = this->last_position.has_value() ? *this->last_position : Point::Zero(); - GCode::SmoothPath smooth_path = smooth_path_cache.resolve_or_fit_split_with_seam( - loop_src, false, m_scaled_resolution, seam_point, scaled(0.0015)); + GCode::SmoothPath smooth_path = smooth_path_cache.resolve_or_fit_split_with_seam(loop_src, reverse_loop, m_scaled_resolution, seam_point, scaled(0.0015)); // Clip the path to avoid the extruder to get exactly on the first point of the loop; // if polyline was shorter than the clipping distance we'd get a null polyline, so diff --git a/src/libslic3r/GCode/Wipe.cpp b/src/libslic3r/GCode/Wipe.cpp index b5f9151b03..e66f385692 100644 --- a/src/libslic3r/GCode/Wipe.cpp +++ b/src/libslic3r/GCode/Wipe.cpp @@ -187,7 +187,7 @@ std::string Wipe::wipe(GCodeGenerator &gcodegen, bool toolchange) // Make a little move inwards before leaving loop after path was extruded, // thus the current extruder position is at the end of a path and the path // may not be closed in case the loop was clipped to hide a seam. -std::optional wipe_hide_seam(const SmoothPath &path, bool is_hole, double wipe_length) +std::optional wipe_hide_seam(const SmoothPath &path, const bool path_reversed, const double wipe_length) { assert(! path.empty()); assert(path.front().path.size() >= 2); @@ -222,7 +222,7 @@ std::optional wipe_hide_seam(const SmoothPath &path, bool is_hole, double double angle_inside = angle(p_next - p_current, p_prev - p_current); assert(angle_inside >= -M_PI && angle_inside <= M_PI); // 3rd of this angle will be taken, thus make the angle monotonic before interpolation. - if (is_hole) { + if (path_reversed) { if (angle_inside > 0) angle_inside -= 2.0 * M_PI; } else { diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 6e34c514d6..27612f5281 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -506,7 +506,7 @@ static std::vector s_Preset_machine_limits_options { static std::vector s_Preset_printer_options { "printer_technology", "autoemit_temperature_commands", "bed_shape", "bed_custom_texture", "bed_custom_model", "binary_gcode", "z_offset", "gcode_flavor", "use_relative_e_distances", - "use_firmware_retraction", "use_volumetric_e", "variable_layer_height", + "use_firmware_retraction", "use_volumetric_e", "variable_layer_height", "prefer_clockwise_movements", //FIXME the print host keys are left here just for conversion from the Printer preset to Physical Printer preset. "host_type", "print_host", "printhost_apikey", "printhost_cafile", "single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode", diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 2cf539c966..62dc11d0a7 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -211,7 +211,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n // Therefore toggling the Spiral Vase on / off requires complete reslicing. || opt_key == "spiral_vase" || opt_key == "filament_shrinkage_compensation_xy" - || opt_key == "filament_shrinkage_compensation_z") { + || opt_key == "filament_shrinkage_compensation_z" + || opt_key == "prefer_clockwise_movements") { osteps.emplace_back(posSlice); } else if ( opt_key == "complete_objects" diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index b14ff3e11b..069b4fbd4e 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3366,6 +3366,13 @@ void PrintConfigDef::init_fff_params() def->mode = comExpert; def->set_default_value(new ConfigOptionBool(true)); + def = this->add("prefer_clockwise_movements", coBool); + def->label = L("Prefer clockwise movements"); + def->tooltip = L("This setting makes the printer print loops clockwise instead of counterclockwise. " + "Most printers don't need to print loops clockwise."); + def->mode = comExpert; + def->set_default_value(new ConfigOptionBool(false)); + def = this->add("wipe", coBools); def->label = L("Wipe while retracting"); def->tooltip = L("This flag will move the nozzle while retracting to minimize the possible blob " diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index e95f486776..e36995842e 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -864,6 +864,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionString, output_filename_format)) ((ConfigOptionFloat, perimeter_acceleration)) ((ConfigOptionStrings, post_process)) + ((ConfigOptionBool, prefer_clockwise_movements)) ((ConfigOptionString, printer_model)) ((ConfigOptionString, printer_notes)) ((ConfigOptionFloat, resolution)) diff --git a/src/libslic3r/Support/SupportCommon.cpp b/src/libslic3r/Support/SupportCommon.cpp index 64a6924a3c..bc8f53e74d 100644 --- a/src/libslic3r/Support/SupportCommon.cpp +++ b/src/libslic3r/Support/SupportCommon.cpp @@ -519,18 +519,19 @@ static inline void fill_expolygons_generate_paths( fill_expolygons_generate_paths(dst, std::move(expolygons), filler, fill_params, density, role, flow); } -static Polylines draw_perimeters(const ExPolygon &expoly, double clip_length) -{ +static Polylines draw_perimeters(const ExPolygon &expoly, double clip_length, const bool prefer_clockwise_movements) { // Draw the perimeters. Polylines polylines; polylines.reserve(expoly.holes.size() + 1); for (size_t i = 0; i <= expoly.holes.size(); ++ i) { Polyline pl(i == 0 ? expoly.contour.points : expoly.holes[i - 1].points); pl.points.emplace_back(pl.points.front()); - if (i > 0) - // It is a hole, reverse it. + + // When prefer_clockwise_movements is true ensure that all loops are CW oriented (reverse contour), + // otherwise ensure that all loops are CCW oriented (reverse loops). + if (const bool loop_reverse = prefer_clockwise_movements ? i == 0 : i > 0; loop_reverse) pl.reverse(); - // so that all contours are CCW oriented. + pl.clip_end(clip_length); polylines.emplace_back(std::move(pl)); } @@ -647,7 +648,7 @@ static inline void tree_supports_generate_paths( ExPolygons level2 = offset2_ex({ expoly }, -1.5 * flow.scaled_width(), 0.5 * flow.scaled_width()); if (level2.size() == 1) { Polylines polylines; - extrusion_entities_append_paths(eec->entities, draw_perimeters(expoly, clip_length), { ExtrusionRole::SupportMaterial, flow }, + extrusion_entities_append_paths(eec->entities, draw_perimeters(expoly, clip_length, support_params.prefer_clockwise_movements), { ExtrusionRole::SupportMaterial, flow }, // Disable reversal of the path, always start with the anchor, always print CCW. false); expoly = level2.front(); @@ -659,10 +660,14 @@ static inline void tree_supports_generate_paths( // The anchor candidate points are annotated with an index of the source contour or with -1 if on intersection. anchor_candidates.clear(); shrink_expolygon_with_contour_idx(expoly, flow.scaled_width(), DefaultJoinType, 1.2, anchor_candidates); - // Orient all contours CW. - for (auto &path : anchor_candidates) - if (ClipperLib_Z::Area(path) > 0) + + // Based on the way how loops with anchors are constructed (we reverse orientation at the end), + // orient all loops CCW when prefer_clockwise_movements is true and orient all loops CW otherwise. + for (auto &path : anchor_candidates) { + const bool is_ccw = ClipperLib_Z::Area(path) > 0; + if (const bool path_reverse = support_params.prefer_clockwise_movements ? !is_ccw : is_ccw; path_reverse) std::reverse(path.begin(), path.end()); + } // Draw the perimeters. Polylines polylines; @@ -671,10 +676,12 @@ static inline void tree_supports_generate_paths( // Open the loop with a seam. const Polygon &loop = expoly.contour_or_hole(idx_loop); Polyline pl(loop.points); - // Orient all contours CW, because the anchor will be added to the end of polyline while we want to start a loop with the anchor. - if (idx_loop == 0) - // It is an outer contour. + + // When prefer_clockwise_movements is true, orient all loops CCW and orient all loops CW otherwise + // because the anchor will be added to the end of the polyline while we want to start a loop with the anchor. + if (const bool loop_reverse = support_params.prefer_clockwise_movements ? idx_loop != 0 : idx_loop == 0; loop_reverse) pl.reverse(); + pl.points.emplace_back(pl.points.front()); pl.clip_end(clip_length); if (pl.size() < 2) @@ -767,11 +774,12 @@ static inline void fill_expolygons_with_sheath_generate_paths( ExtrusionEntitiesPtr &dst, const Polygons &polygons, Fill *filler, - float density, - ExtrusionRole role, + const float density, + const ExtrusionRole role, const Flow &flow, - bool with_sheath, - bool no_sort) + const bool with_sheath, + const bool no_sort, + const bool prefer_clockwise_movements) { if (polygons.empty()) return; @@ -797,7 +805,7 @@ static inline void fill_expolygons_with_sheath_generate_paths( eec->no_sort = true; } ExtrusionEntitiesPtr &out = no_sort ? eec->entities : dst; - extrusion_entities_append_paths(out, draw_perimeters(expoly, clip_length), { ExtrusionRole::SupportMaterial, flow }); + extrusion_entities_append_paths(out, draw_perimeters(expoly, clip_length, prefer_clockwise_movements), { ExtrusionRole::SupportMaterial, flow }, false); // Fill in the rest. fill_expolygons_generate_paths(out, offset_ex(expoly, float(-0.4 * spacing)), filler, fill_params, density, role, flow); if (no_sort && ! eec->empty()) @@ -1526,7 +1534,7 @@ void generate_support_toolpaths( filler, float(support_params.support_density), // Extrusion parameters ExtrusionRole::SupportMaterial, flow, - support_params.with_sheath, false); + support_params.with_sheath, false, support_params.prefer_clockwise_movements); } if (! tree_polygons.empty()) tree_supports_generate_paths(support_layer.support_fills.entities, tree_polygons, flow, support_params); @@ -1561,7 +1569,7 @@ void generate_support_toolpaths( // Extrusion parameters (support_layer_id < slicing_params.base_raft_layers) ? ExtrusionRole::SupportMaterial : ExtrusionRole::SupportMaterialInterface, flow, // sheath at first layer - support_layer_id == 0, support_layer_id == 0); + support_layer_id == 0, support_layer_id == 0, support_params.prefer_clockwise_movements); } }); @@ -1802,7 +1810,7 @@ void generate_support_toolpaths( filler, density, // Extrusion parameters ExtrusionRole::SupportMaterial, flow, - sheath, no_sort); + sheath, no_sort, support_params.prefer_clockwise_movements); } // Merge base_interface_layers to base_layers to avoid unneccessary retractions diff --git a/src/libslic3r/Support/SupportParameters.cpp b/src/libslic3r/Support/SupportParameters.cpp index de58a640a0..f932567bc3 100644 --- a/src/libslic3r/Support/SupportParameters.cpp +++ b/src/libslic3r/Support/SupportParameters.cpp @@ -143,6 +143,8 @@ SupportParameters::SupportParameters(const PrintObject &object) } this->tree_branch_diameter_double_wall_area_scaled = 0.25 * sqr(scaled(object_config.support_tree_branch_diameter_double_wall.value)) * M_PI; + + this->prefer_clockwise_movements = print_config.prefer_clockwise_movements; } } // namespace Slic3r diff --git a/src/libslic3r/Support/SupportParameters.hpp b/src/libslic3r/Support/SupportParameters.hpp index b042e7640f..f43b9091e8 100644 --- a/src/libslic3r/Support/SupportParameters.hpp +++ b/src/libslic3r/Support/SupportParameters.hpp @@ -88,6 +88,9 @@ struct SupportParameters { float raft_angle_base; float raft_angle_interface; + // Print closed loop clockwise when it is equal to true. + bool prefer_clockwise_movements; + // 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.)); } diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 8fc6db7aaf..c70b940605 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2785,6 +2785,7 @@ void TabPrinter::build_fff() optgroup->append_single_option_line("use_firmware_retraction"); optgroup->append_single_option_line("use_volumetric_e"); optgroup->append_single_option_line("variable_layer_height"); + optgroup->append_single_option_line("prefer_clockwise_movements"); const int gcode_field_height = 15; // 150 const int notes_field_height = 25; // 250