mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-07-30 21:12:01 +08:00
SPE-2301: Add a new option that makes a printer print loops clockwise instead of counterclockwise.
This commit is contained in:
parent
c2eac9502f
commit
d6136a34e3
@ -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()));
|
||||
|
@ -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<FillParams>::value, "FillParams class is not POD (and it should be - see constructor).");
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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<double>(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<double>(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<Point> pt = wipe_hide_seam(smooth_path, is_hole, scale_(EXTRUDER_CONFIG(nozzle_diameter))); pt) {
|
||||
if (std::optional<Point> 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<double>(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<double>(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
|
||||
|
@ -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<Point> wipe_hide_seam(const SmoothPath &path, bool is_hole, double wipe_length)
|
||||
std::optional<Point> 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<Point> 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 {
|
||||
|
@ -506,7 +506,7 @@ static std::vector<std::string> s_Preset_machine_limits_options {
|
||||
static std::vector<std::string> 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",
|
||||
|
@ -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"
|
||||
|
@ -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 "
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
|
@ -143,6 +143,8 @@ SupportParameters::SupportParameters(const PrintObject &object)
|
||||
}
|
||||
|
||||
this->tree_branch_diameter_double_wall_area_scaled = 0.25 * sqr(scaled<double>(object_config.support_tree_branch_diameter_double_wall.value)) * M_PI;
|
||||
|
||||
this->prefer_clockwise_movements = print_config.prefer_clockwise_movements;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
@ -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.)); }
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user