mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-02 04:30:39 +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
|
// apply half spacing using this flow's own spacing and generate infill
|
||||||
FillParams params;
|
FillParams params;
|
||||||
params.density = float(0.01 * surface_fill.params.density);
|
params.density = float(0.01 * surface_fill.params.density);
|
||||||
params.dont_adjust = false; // surface_fill.params.dont_adjust;
|
params.dont_adjust = false; // surface_fill.params.dont_adjust;
|
||||||
params.anchor_length = surface_fill.params.anchor_length;
|
params.anchor_length = surface_fill.params.anchor_length;
|
||||||
params.anchor_length_max = surface_fill.params.anchor_length_max;
|
params.anchor_length_max = surface_fill.params.anchor_length_max;
|
||||||
params.resolution = resolution;
|
params.resolution = resolution;
|
||||||
params.use_arachne = (perimeter_generator == PerimeterGeneratorType::Arachne && surface_fill.params.pattern == ipConcentric) || surface_fill.params.pattern == ipEnsuring;
|
params.use_arachne = (perimeter_generator == PerimeterGeneratorType::Arachne && surface_fill.params.pattern == ipConcentric) || surface_fill.params.pattern == ipEnsuring;
|
||||||
params.layer_height = layerm.layer()->height;
|
params.layer_height = layerm.layer()->height;
|
||||||
|
params.prefer_clockwise_movements = this->object()->print()->config().prefer_clockwise_movements;
|
||||||
|
|
||||||
for (ExPolygon &expoly : surface_fill.expolygons) {
|
for (ExPolygon &expoly : surface_fill.expolygons) {
|
||||||
// Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon.
|
// 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();
|
thick_polylines.clear();
|
||||||
} else {
|
} 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(
|
extrusion_entities_append_paths(
|
||||||
eec->entities, std::move(polylines),
|
eec->entities, std::move(polylines),
|
||||||
ExtrusionAttributes{ surface_fill.params.extrusion_role,
|
ExtrusionAttributes{ surface_fill.params.extrusion_role,
|
||||||
ExtrusionFlow{ flow_mm3_per_mm, float(flow_width), surface_fill.params.flow.height() }
|
ExtrusionFlow{ flow_mm3_per_mm, float(flow_width), surface_fill.params.flow.height() }
|
||||||
});
|
}, !params.prefer_clockwise_movements);
|
||||||
layerm.m_fills.entities.push_back(eec);
|
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()));
|
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 };
|
bool use_arachne { false };
|
||||||
// Layer height for Concentric infill with Arachne.
|
// Layer height for Concentric infill with Arachne.
|
||||||
coordf_t layer_height { 0.f };
|
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).");
|
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
|
// clip the paths to prevent the extruder from getting exactly on the first point of the loop
|
||||||
// Keep valid paths only.
|
// Keep valid paths only.
|
||||||
size_t j = iPathFirst;
|
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);
|
polylines_out[i].clip_end(this->loop_clipping);
|
||||||
if (polylines_out[i].is_valid()) {
|
if (polylines_out[i].is_valid()) {
|
||||||
|
if (params.prefer_clockwise_movements)
|
||||||
|
polylines_out[i].reverse();
|
||||||
|
|
||||||
if (j < i)
|
if (j < i)
|
||||||
polylines_out[j] = std::move(polylines_out[i]);
|
polylines_out[j] = std::move(polylines_out[i]);
|
||||||
++ j;
|
|
||||||
|
++j;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (j < polylines_out.size())
|
if (j < polylines_out.size())
|
||||||
polylines_out.erase(polylines_out.begin() + int(j), polylines_out.end());
|
polylines_out.erase(polylines_out.begin() + int(j), polylines_out.end());
|
||||||
//TODO: return ExtrusionLoop objects to get better chained paths,
|
//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) {
|
for (Arachne::VariableWidthLines &loop : loops) {
|
||||||
if (loop.empty())
|
if (loop.empty())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
for (const Arachne::ExtrusionLine &wall : loop)
|
for (const Arachne::ExtrusionLine &wall : loop)
|
||||||
all_extrusions.emplace_back(&wall);
|
all_extrusions.emplace_back(&wall);
|
||||||
}
|
}
|
||||||
@ -106,8 +112,14 @@ void FillConcentric::_fill_surface_single(const FillParams ¶ms,
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
ThickPolyline thick_polyline = Arachne::to_thick_polyline(*extrusion);
|
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_polyline.start_at_index(nearest_point_index(thick_polyline.points, last_pos));
|
||||||
|
}
|
||||||
|
|
||||||
thick_polylines_out.emplace_back(std::move(thick_polyline));
|
thick_polylines_out.emplace_back(std::move(thick_polyline));
|
||||||
last_pos = thick_polylines_out.back().last_point();
|
last_pos = thick_polylines_out.back().last_point();
|
||||||
}
|
}
|
||||||
|
@ -308,19 +308,25 @@ ThickPolylines make_fill_polylines(
|
|||||||
for (Arachne::VariableWidthLines &loop : loops) {
|
for (Arachne::VariableWidthLines &loop : loops) {
|
||||||
if (loop.empty())
|
if (loop.empty())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
for (const Arachne::ExtrusionLine &wall : loop)
|
for (const Arachne::ExtrusionLine &wall : loop)
|
||||||
all_extrusions.emplace_back(&wall);
|
all_extrusions.emplace_back(&wall);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const Arachne::ExtrusionLine *extrusion : all_extrusions) {
|
for (const Arachne::ExtrusionLine *extrusion : all_extrusions) {
|
||||||
if (extrusion->junctions.size() < 2) {
|
if (extrusion->junctions.size() < 2)
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
ThickPolyline thick_polyline = Arachne::to_thick_polyline(*extrusion);
|
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, ex_bb.min));
|
thick_polyline.start_at_index(nearest_point_index(thick_polyline.points, ex_bb.min));
|
||||||
thick_polyline.clip_end(scaled_spacing * 0.5);
|
thick_polyline.clip_end(scaled_spacing * 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (thick_polyline.is_valid() && thick_polyline.length() > 0 && thick_polyline.points.size() > 1) {
|
if (thick_polyline.is_valid() && thick_polyline.length() > 0 && thick_polyline.points.size() > 1) {
|
||||||
thick_polylines.push_back(thick_polyline);
|
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)
|
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.
|
// Extrude all loops CCW unless CW movements are prefered.
|
||||||
bool is_hole = loop_src.is_clockwise();
|
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();
|
Point seam_point = this->last_position.has_value() ? *this->last_position : Point::Zero();
|
||||||
if (!m_config.spiral_vase && comment_is_perimeter(description)) {
|
if (!m_config.spiral_vase && comment_is_perimeter(description)) {
|
||||||
assert(m_layer != nullptr);
|
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,
|
// 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.
|
// 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(
|
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));
|
||||||
loop_src, is_hole, 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;
|
// 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
|
// 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) {
|
} 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.
|
// Only wipe inside if the wipe along the perimeter is disabled.
|
||||||
// Make a little move inwards before leaving loop.
|
// 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.
|
// Generate the seam hiding travel move.
|
||||||
gcode += m_writer.travel_to_xy(this->point_to_gcode(*pt), "move inwards before travel");
|
gcode += m_writer.travel_to_xy(this->point_to_gcode(*pt), "move inwards before travel");
|
||||||
this->last_position = *pt;
|
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)
|
const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed)
|
||||||
{
|
{
|
||||||
assert(loop_src.is_counter_clockwise());
|
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();
|
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(
|
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));
|
||||||
loop_src, false, 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;
|
// 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
|
// 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,
|
// 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
|
// 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.
|
// 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.empty());
|
||||||
assert(path.front().path.size() >= 2);
|
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);
|
double angle_inside = angle(p_next - p_current, p_prev - p_current);
|
||||||
assert(angle_inside >= -M_PI && angle_inside <= M_PI);
|
assert(angle_inside >= -M_PI && angle_inside <= M_PI);
|
||||||
// 3rd of this angle will be taken, thus make the angle monotonic before interpolation.
|
// 3rd of this angle will be taken, thus make the angle monotonic before interpolation.
|
||||||
if (is_hole) {
|
if (path_reversed) {
|
||||||
if (angle_inside > 0)
|
if (angle_inside > 0)
|
||||||
angle_inside -= 2.0 * M_PI;
|
angle_inside -= 2.0 * M_PI;
|
||||||
} else {
|
} else {
|
||||||
|
@ -506,7 +506,7 @@ static std::vector<std::string> s_Preset_machine_limits_options {
|
|||||||
static std::vector<std::string> s_Preset_printer_options {
|
static std::vector<std::string> s_Preset_printer_options {
|
||||||
"printer_technology", "autoemit_temperature_commands",
|
"printer_technology", "autoemit_temperature_commands",
|
||||||
"bed_shape", "bed_custom_texture", "bed_custom_model", "binary_gcode", "z_offset", "gcode_flavor", "use_relative_e_distances",
|
"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.
|
//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",
|
"host_type", "print_host", "printhost_apikey", "printhost_cafile",
|
||||||
"single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode",
|
"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.
|
// Therefore toggling the Spiral Vase on / off requires complete reslicing.
|
||||||
|| opt_key == "spiral_vase"
|
|| opt_key == "spiral_vase"
|
||||||
|| opt_key == "filament_shrinkage_compensation_xy"
|
|| 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);
|
osteps.emplace_back(posSlice);
|
||||||
} else if (
|
} else if (
|
||||||
opt_key == "complete_objects"
|
opt_key == "complete_objects"
|
||||||
|
@ -3366,6 +3366,13 @@ void PrintConfigDef::init_fff_params()
|
|||||||
def->mode = comExpert;
|
def->mode = comExpert;
|
||||||
def->set_default_value(new ConfigOptionBool(true));
|
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 = this->add("wipe", coBools);
|
||||||
def->label = L("Wipe while retracting");
|
def->label = L("Wipe while retracting");
|
||||||
def->tooltip = L("This flag will move the nozzle while retracting to minimize the possible blob "
|
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))
|
((ConfigOptionString, output_filename_format))
|
||||||
((ConfigOptionFloat, perimeter_acceleration))
|
((ConfigOptionFloat, perimeter_acceleration))
|
||||||
((ConfigOptionStrings, post_process))
|
((ConfigOptionStrings, post_process))
|
||||||
|
((ConfigOptionBool, prefer_clockwise_movements))
|
||||||
((ConfigOptionString, printer_model))
|
((ConfigOptionString, printer_model))
|
||||||
((ConfigOptionString, printer_notes))
|
((ConfigOptionString, printer_notes))
|
||||||
((ConfigOptionFloat, resolution))
|
((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);
|
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.
|
// Draw the perimeters.
|
||||||
Polylines polylines;
|
Polylines polylines;
|
||||||
polylines.reserve(expoly.holes.size() + 1);
|
polylines.reserve(expoly.holes.size() + 1);
|
||||||
for (size_t i = 0; i <= expoly.holes.size(); ++ i) {
|
for (size_t i = 0; i <= expoly.holes.size(); ++ i) {
|
||||||
Polyline pl(i == 0 ? expoly.contour.points : expoly.holes[i - 1].points);
|
Polyline pl(i == 0 ? expoly.contour.points : expoly.holes[i - 1].points);
|
||||||
pl.points.emplace_back(pl.points.front());
|
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();
|
pl.reverse();
|
||||||
// so that all contours are CCW oriented.
|
|
||||||
pl.clip_end(clip_length);
|
pl.clip_end(clip_length);
|
||||||
polylines.emplace_back(std::move(pl));
|
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());
|
ExPolygons level2 = offset2_ex({ expoly }, -1.5 * flow.scaled_width(), 0.5 * flow.scaled_width());
|
||||||
if (level2.size() == 1) {
|
if (level2.size() == 1) {
|
||||||
Polylines polylines;
|
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.
|
// Disable reversal of the path, always start with the anchor, always print CCW.
|
||||||
false);
|
false);
|
||||||
expoly = level2.front();
|
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.
|
// The anchor candidate points are annotated with an index of the source contour or with -1 if on intersection.
|
||||||
anchor_candidates.clear();
|
anchor_candidates.clear();
|
||||||
shrink_expolygon_with_contour_idx(expoly, flow.scaled_width(), DefaultJoinType, 1.2, anchor_candidates);
|
shrink_expolygon_with_contour_idx(expoly, flow.scaled_width(), DefaultJoinType, 1.2, anchor_candidates);
|
||||||
// Orient all contours CW.
|
|
||||||
for (auto &path : anchor_candidates)
|
// Based on the way how loops with anchors are constructed (we reverse orientation at the end),
|
||||||
if (ClipperLib_Z::Area(path) > 0)
|
// 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());
|
std::reverse(path.begin(), path.end());
|
||||||
|
}
|
||||||
|
|
||||||
// Draw the perimeters.
|
// Draw the perimeters.
|
||||||
Polylines polylines;
|
Polylines polylines;
|
||||||
@ -671,10 +676,12 @@ static inline void tree_supports_generate_paths(
|
|||||||
// Open the loop with a seam.
|
// Open the loop with a seam.
|
||||||
const Polygon &loop = expoly.contour_or_hole(idx_loop);
|
const Polygon &loop = expoly.contour_or_hole(idx_loop);
|
||||||
Polyline pl(loop.points);
|
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)
|
// When prefer_clockwise_movements is true, orient all loops CCW and orient all loops CW otherwise
|
||||||
// It is an outer contour.
|
// 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.reverse();
|
||||||
|
|
||||||
pl.points.emplace_back(pl.points.front());
|
pl.points.emplace_back(pl.points.front());
|
||||||
pl.clip_end(clip_length);
|
pl.clip_end(clip_length);
|
||||||
if (pl.size() < 2)
|
if (pl.size() < 2)
|
||||||
@ -767,11 +774,12 @@ static inline void fill_expolygons_with_sheath_generate_paths(
|
|||||||
ExtrusionEntitiesPtr &dst,
|
ExtrusionEntitiesPtr &dst,
|
||||||
const Polygons &polygons,
|
const Polygons &polygons,
|
||||||
Fill *filler,
|
Fill *filler,
|
||||||
float density,
|
const float density,
|
||||||
ExtrusionRole role,
|
const ExtrusionRole role,
|
||||||
const Flow &flow,
|
const Flow &flow,
|
||||||
bool with_sheath,
|
const bool with_sheath,
|
||||||
bool no_sort)
|
const bool no_sort,
|
||||||
|
const bool prefer_clockwise_movements)
|
||||||
{
|
{
|
||||||
if (polygons.empty())
|
if (polygons.empty())
|
||||||
return;
|
return;
|
||||||
@ -797,7 +805,7 @@ static inline void fill_expolygons_with_sheath_generate_paths(
|
|||||||
eec->no_sort = true;
|
eec->no_sort = true;
|
||||||
}
|
}
|
||||||
ExtrusionEntitiesPtr &out = no_sort ? eec->entities : dst;
|
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 in the rest.
|
||||||
fill_expolygons_generate_paths(out, offset_ex(expoly, float(-0.4 * spacing)), filler, fill_params, density, role, flow);
|
fill_expolygons_generate_paths(out, offset_ex(expoly, float(-0.4 * spacing)), filler, fill_params, density, role, flow);
|
||||||
if (no_sort && ! eec->empty())
|
if (no_sort && ! eec->empty())
|
||||||
@ -1526,7 +1534,7 @@ void generate_support_toolpaths(
|
|||||||
filler, float(support_params.support_density),
|
filler, float(support_params.support_density),
|
||||||
// Extrusion parameters
|
// Extrusion parameters
|
||||||
ExtrusionRole::SupportMaterial, flow,
|
ExtrusionRole::SupportMaterial, flow,
|
||||||
support_params.with_sheath, false);
|
support_params.with_sheath, false, support_params.prefer_clockwise_movements);
|
||||||
}
|
}
|
||||||
if (! tree_polygons.empty())
|
if (! tree_polygons.empty())
|
||||||
tree_supports_generate_paths(support_layer.support_fills.entities, tree_polygons, flow, support_params);
|
tree_supports_generate_paths(support_layer.support_fills.entities, tree_polygons, flow, support_params);
|
||||||
@ -1561,7 +1569,7 @@ void generate_support_toolpaths(
|
|||||||
// Extrusion parameters
|
// Extrusion parameters
|
||||||
(support_layer_id < slicing_params.base_raft_layers) ? ExtrusionRole::SupportMaterial : ExtrusionRole::SupportMaterialInterface, flow,
|
(support_layer_id < slicing_params.base_raft_layers) ? ExtrusionRole::SupportMaterial : ExtrusionRole::SupportMaterialInterface, flow,
|
||||||
// sheath at first layer
|
// 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,
|
filler, density,
|
||||||
// Extrusion parameters
|
// Extrusion parameters
|
||||||
ExtrusionRole::SupportMaterial, flow,
|
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
|
// 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->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
|
} // namespace Slic3r
|
||||||
|
@ -88,6 +88,9 @@ struct SupportParameters {
|
|||||||
float raft_angle_base;
|
float raft_angle_base;
|
||||||
float raft_angle_interface;
|
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()
|
// Produce a raft interface angle for a given SupportLayer::interface_id()
|
||||||
float raft_interface_angle(size_t interface_id) const
|
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.)); }
|
{ 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_firmware_retraction");
|
||||||
optgroup->append_single_option_line("use_volumetric_e");
|
optgroup->append_single_option_line("use_volumetric_e");
|
||||||
optgroup->append_single_option_line("variable_layer_height");
|
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 gcode_field_height = 15; // 150
|
||||||
const int notes_field_height = 25; // 250
|
const int notes_field_height = 25; // 250
|
||||||
|
Loading…
x
Reference in New Issue
Block a user