ArcWelder: Smooth interpolation of skirt & brim

Lower resolution (higher decimation rate) for
sparse infill: 3x
support: 4x
skirt+brim: 4x
This commit is contained in:
Vojtech Bubnik 2023-07-18 11:31:23 +02:00
parent 3d439c617c
commit bde6fb2528
6 changed files with 172 additions and 58 deletions

View File

@ -82,6 +82,7 @@ struct ExtrusionRole : public ExtrusionRoleModifiers
bool is_external_perimeter() const { return this->is_perimeter() && this->is_external(); } bool is_external_perimeter() const { return this->is_perimeter() && this->is_external(); }
bool is_infill() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Infill); } bool is_infill() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Infill); }
bool is_solid_infill() const { return this->is_infill() && this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Solid); } bool is_solid_infill() const { return this->is_infill() && this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Solid); }
bool is_sparse_infill() const { return this->is_infill() && ! this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Solid); }
bool is_external() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::External); } bool is_external() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::External); }
bool is_bridge() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Bridge); } bool is_bridge() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Bridge); }
@ -89,6 +90,9 @@ struct ExtrusionRole : public ExtrusionRoleModifiers
bool is_support_base() const { return this->is_support() && ! this->is_external(); } bool is_support_base() const { return this->is_support() && ! this->is_external(); }
bool is_support_interface() const { return this->is_support() && this->is_external(); } bool is_support_interface() const { return this->is_support() && this->is_external(); }
bool is_mixed() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Mixed); } bool is_mixed() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Mixed); }
// Brim is currently marked as skirt.
bool is_skirt() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Skirt); }
}; };
// Special flags describing loop // Special flags describing loop

View File

@ -798,6 +798,20 @@ std::vector<const PrintInstance*> sort_object_instances_by_model_order(const Pri
return instances; return instances;
} }
static inline GCode::SmoothPathCache smooth_path_interpolate_global(const Print& print)
{
const GCode::SmoothPathCache::InterpolationParameters interpolation_params {
scaled<double>(print.config().gcode_resolution.value),
print.config().arc_fitting != ArcFittingType::Disabled && ! print.config().spiral_vase ?
Geometry::ArcWelder::default_arc_length_percent_tolerance :
0
};
GCode::SmoothPathCache out;
out.interpolate_add(print.skirt(), interpolation_params);
out.interpolate_add(print.brim(), interpolation_params);
return out;
}
void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGeneratorCallback thumbnail_cb) void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGeneratorCallback thumbnail_cb)
{ {
// modifies m_silent_time_estimator_enabled // modifies m_silent_time_estimator_enabled
@ -1058,6 +1072,8 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
file.write(this->set_extruder(initial_extruder_id, 0.)); file.write(this->set_extruder(initial_extruder_id, 0.));
} }
GCode::SmoothPathCache smooth_path_cache_global = smooth_path_interpolate_global(print);
// Do all objects for each layer. // Do all objects for each layer.
if (print.config().complete_objects.value) { if (print.config().complete_objects.value) {
size_t finished_objects = 0; size_t finished_objects = 0;
@ -1102,7 +1118,9 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
// Process all layers of a single object instance (sequential mode) with a parallel pipeline: // Process all layers of a single object instance (sequential mode) with a parallel pipeline:
// Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser
// and export G-code into file. // and export G-code into file.
this->process_layers(print, tool_ordering, collect_layers_to_print(object), *print_object_instance_sequential_active - object.instances().data(), file); this->process_layers(print, tool_ordering, collect_layers_to_print(object),
*print_object_instance_sequential_active - object.instances().data(),
smooth_path_cache_global, file);
++ finished_objects; ++ finished_objects;
// Flag indicating whether the nozzle temperature changes from 1st to 2nd layer were performed. // Flag indicating whether the nozzle temperature changes from 1st to 2nd layer were performed.
// Reset it when starting another object from 1st layer. // Reset it when starting another object from 1st layer.
@ -1158,7 +1176,8 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
// Process all layers of all objects (non-sequential mode) with a parallel pipeline: // Process all layers of all objects (non-sequential mode) with a parallel pipeline:
// Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser
// and export G-code into file. // and export G-code into file.
this->process_layers(print, tool_ordering, print_object_instances_ordering, layers_to_print, file); this->process_layers(print, tool_ordering, print_object_instances_ordering, layers_to_print,
smooth_path_cache_global, file);
if (m_wipe_tower) if (m_wipe_tower)
// Purge the extruder, pull out the active filament. // Purge the extruder, pull out the active filament.
file.write(m_wipe_tower->finalize(*this)); file.write(m_wipe_tower->finalize(*this));
@ -1258,6 +1277,7 @@ void GCodeGenerator::process_layers(
const ToolOrdering &tool_ordering, const ToolOrdering &tool_ordering,
const std::vector<const PrintInstance*> &print_object_instances_ordering, const std::vector<const PrintInstance*> &print_object_instances_ordering,
const std::vector<std::pair<coordf_t, ObjectsLayerToPrint>> &layers_to_print, const std::vector<std::pair<coordf_t, ObjectsLayerToPrint>> &layers_to_print,
const GCode::SmoothPathCache &smooth_path_cache_global,
GCodeOutputStream &output_stream) GCodeOutputStream &output_stream)
{ {
size_t layer_to_print_idx = 0; size_t layer_to_print_idx = 0;
@ -1268,7 +1288,7 @@ void GCodeGenerator::process_layers(
0 0
}; };
const auto smooth_path_interpolator = tbb::make_filter<void, std::pair<size_t, GCode::SmoothPathCache>>(slic3r_tbb_filtermode::serial_in_order, const auto smooth_path_interpolator = tbb::make_filter<void, std::pair<size_t, GCode::SmoothPathCache>>(slic3r_tbb_filtermode::serial_in_order,
[this, &print, &layers_to_print, &layer_to_print_idx, interpolation_params](tbb::flow_control &fc) -> std::pair<size_t, GCode::SmoothPathCache> { [this, &print, &layers_to_print, &layer_to_print_idx, &interpolation_params](tbb::flow_control &fc) -> std::pair<size_t, GCode::SmoothPathCache> {
if (layer_to_print_idx >= layers_to_print.size()) { if (layer_to_print_idx >= layers_to_print.size()) {
if ((!m_pressure_equalizer && layer_to_print_idx == layers_to_print.size()) || (m_pressure_equalizer && layer_to_print_idx == (layers_to_print.size() + 1))) { if ((!m_pressure_equalizer && layer_to_print_idx == layers_to_print.size()) || (m_pressure_equalizer && layer_to_print_idx == (layers_to_print.size() + 1))) {
fc.stop(); fc.stop();
@ -1288,7 +1308,8 @@ void GCodeGenerator::process_layers(
} }
}); });
const auto generator = tbb::make_filter<std::pair<size_t, GCode::SmoothPathCache>, LayerResult>(slic3r_tbb_filtermode::serial_in_order, const auto generator = tbb::make_filter<std::pair<size_t, GCode::SmoothPathCache>, LayerResult>(slic3r_tbb_filtermode::serial_in_order,
[this, &print, &tool_ordering, &print_object_instances_ordering, &layers_to_print](std::pair<size_t, GCode::SmoothPathCache> in) -> LayerResult { [this, &print, &tool_ordering, &print_object_instances_ordering, &layers_to_print, &smooth_path_cache_global](
std::pair<size_t, GCode::SmoothPathCache> in) -> LayerResult {
size_t layer_to_print_idx = in.first; size_t layer_to_print_idx = in.first;
if (layer_to_print_idx == layers_to_print.size()) { if (layer_to_print_idx == layers_to_print.size()) {
// Pressure equalizer need insert empty input. Because it returns one layer back. // Pressure equalizer need insert empty input. Because it returns one layer back.
@ -1300,7 +1321,9 @@ void GCodeGenerator::process_layers(
if (m_wipe_tower && layer_tools.has_wipe_tower) if (m_wipe_tower && layer_tools.has_wipe_tower)
m_wipe_tower->next_layer(); m_wipe_tower->next_layer();
print.throw_if_canceled(); print.throw_if_canceled();
return this->process_layer(print, layer.second, layer_tools, &in.second, &layer == &layers_to_print.back(), &print_object_instances_ordering, size_t(-1)); return this->process_layer(print, layer.second, layer_tools,
GCode::SmoothPathCaches{ smooth_path_cache_global, in.second },
&layer == &layers_to_print.back(), &print_object_instances_ordering, size_t(-1));
} }
}); });
// The pipeline is variable: The vase mode filter is optional. // The pipeline is variable: The vase mode filter is optional.
@ -1357,6 +1380,7 @@ void GCodeGenerator::process_layers(
const ToolOrdering &tool_ordering, const ToolOrdering &tool_ordering,
ObjectsLayerToPrint layers_to_print, ObjectsLayerToPrint layers_to_print,
const size_t single_object_idx, const size_t single_object_idx,
const GCode::SmoothPathCache &smooth_path_cache_global,
GCodeOutputStream &output_stream) GCodeOutputStream &output_stream)
{ {
size_t layer_to_print_idx = 0; size_t layer_to_print_idx = 0;
@ -1386,7 +1410,7 @@ void GCodeGenerator::process_layers(
} }
}); });
const auto generator = tbb::make_filter<std::pair<size_t, GCode::SmoothPathCache>, LayerResult>(slic3r_tbb_filtermode::serial_in_order, const auto generator = tbb::make_filter<std::pair<size_t, GCode::SmoothPathCache>, LayerResult>(slic3r_tbb_filtermode::serial_in_order,
[this, &print, &tool_ordering, &layers_to_print, single_object_idx](std::pair<size_t, GCode::SmoothPathCache> in) -> LayerResult { [this, &print, &tool_ordering, &layers_to_print, &smooth_path_cache_global, single_object_idx](std::pair<size_t, GCode::SmoothPathCache> in) -> LayerResult {
size_t layer_to_print_idx = in.first; size_t layer_to_print_idx = in.first;
if (layer_to_print_idx == layers_to_print.size()) { if (layer_to_print_idx == layers_to_print.size()) {
// Pressure equalizer need insert empty input. Because it returns one layer back. // Pressure equalizer need insert empty input. Because it returns one layer back.
@ -1395,7 +1419,9 @@ void GCodeGenerator::process_layers(
} else { } else {
ObjectLayerToPrint &layer = layers_to_print[layer_to_print_idx]; ObjectLayerToPrint &layer = layers_to_print[layer_to_print_idx];
print.throw_if_canceled(); print.throw_if_canceled();
return this->process_layer(print, { std::move(layer) }, tool_ordering.tools_for_layer(layer.print_z()), &in.second, &layer == &layers_to_print.back(), nullptr, single_object_idx); return this->process_layer(print, { std::move(layer) }, tool_ordering.tools_for_layer(layer.print_z()),
GCode::SmoothPathCaches{ smooth_path_cache_global, in.second },
&layer == &layers_to_print.back(), nullptr, single_object_idx);
} }
}); });
// The pipeline is variable: The vase mode filter is optional. // The pipeline is variable: The vase mode filter is optional.
@ -1877,7 +1903,7 @@ LayerResult GCodeGenerator::process_layer(
// Set of object & print layers of the same PrintObject and with the same print_z. // Set of object & print layers of the same PrintObject and with the same print_z.
const ObjectsLayerToPrint &layers, const ObjectsLayerToPrint &layers,
const LayerTools &layer_tools, const LayerTools &layer_tools,
const GCode::SmoothPathCache *smooth_path_cache, const GCode::SmoothPathCaches &smooth_path_caches,
const bool last_layer, const bool last_layer,
// Pairs of PrintObject index and its instance index. // Pairs of PrintObject index and its instance index.
const std::vector<const PrintInstance*> *ordering, const std::vector<const PrintInstance*> *ordering,
@ -2044,13 +2070,11 @@ LayerResult GCodeGenerator::process_layer(
double mm3_per_mm = layer_skirt_flow.mm3_per_mm(); double mm3_per_mm = layer_skirt_flow.mm3_per_mm();
for (size_t i = loops.first; i < loops.second; ++i) { for (size_t i = loops.first; i < loops.second; ++i) {
// Adjust flow according to this layer's layer height. // Adjust flow according to this layer's layer height.
const ExtrusionLoop src = *dynamic_cast<const ExtrusionLoop*>(print.skirt().entities[i]);
ExtrusionLoop loop(src.loop_role());
loop.paths.reserve(src.paths.size());
for (const ExtrusionPath &path : src.paths)
loop.paths.emplace_back(path.polyline, ExtrusionAttributes{ path.role(), ExtrusionFlow{ mm3_per_mm, path.width(), layer_skirt_flow.height() } });
//FIXME using the support_material_speed of the 1st object printed. //FIXME using the support_material_speed of the 1st object printed.
gcode += this->extrude_loop(loop, smooth_path_cache, "skirt"sv, m_config.support_material_speed.value); gcode += this->extrude_skirt(dynamic_cast<const ExtrusionLoop&>(*print.skirt().entities[i]),
// Override of skirt extrusion parameters. extrude_skirt() will fill in the extrusion width.
ExtrusionFlow{ mm3_per_mm, 0., layer_skirt_flow.height() },
smooth_path_caches.global(), "skirt"sv, m_config.support_material_speed.value);
} }
m_avoid_crossing_perimeters.use_external_mp(false); m_avoid_crossing_perimeters.use_external_mp(false);
// Allow a straight travel move to the first object point if this is the first layer (but don't in next layers). // Allow a straight travel move to the first object point if this is the first layer (but don't in next layers).
@ -2063,7 +2087,7 @@ LayerResult GCodeGenerator::process_layer(
this->set_origin(0., 0.); this->set_origin(0., 0.);
m_avoid_crossing_perimeters.use_external_mp(); m_avoid_crossing_perimeters.use_external_mp();
for (const ExtrusionEntity *ee : print.brim().entities) for (const ExtrusionEntity *ee : print.brim().entities)
gcode += this->extrude_entity({ *ee, false }, smooth_path_cache, "brim"sv, m_config.support_material_speed.value); gcode += this->extrude_entity({ *ee, false }, smooth_path_caches.global(), "brim"sv, m_config.support_material_speed.value);
m_brim_done = true; m_brim_done = true;
m_avoid_crossing_perimeters.use_external_mp(false); m_avoid_crossing_perimeters.use_external_mp(false);
// Allow a straight travel move to the first object point. // Allow a straight travel move to the first object point.
@ -2080,7 +2104,7 @@ LayerResult GCodeGenerator::process_layer(
for (const InstanceToPrint &instance : instances_to_print) for (const InstanceToPrint &instance : instances_to_print)
this->process_layer_single_object( this->process_layer_single_object(
gcode, extruder_id, instance, gcode, extruder_id, instance,
layers[instance.object_layer_to_print_id], layer_tools, smooth_path_cache, layers[instance.object_layer_to_print_id], layer_tools, smooth_path_caches.layer_local(),
is_anything_overridden, true /* print_wipe_extrusions */); is_anything_overridden, true /* print_wipe_extrusions */);
if (gcode_size_old < gcode.size()) if (gcode_size_old < gcode.size())
gcode+="; PURGING FINISHED\n"; gcode+="; PURGING FINISHED\n";
@ -2089,7 +2113,7 @@ LayerResult GCodeGenerator::process_layer(
for (const InstanceToPrint &instance : instances_to_print) for (const InstanceToPrint &instance : instances_to_print)
this->process_layer_single_object( this->process_layer_single_object(
gcode, extruder_id, instance, gcode, extruder_id, instance,
layers[instance.object_layer_to_print_id], layer_tools, smooth_path_cache, layers[instance.object_layer_to_print_id], layer_tools, smooth_path_caches.layer_local(),
is_anything_overridden, false /* print_wipe_extrusions */); is_anything_overridden, false /* print_wipe_extrusions */);
} }
@ -2119,7 +2143,7 @@ void GCodeGenerator::process_layer_single_object(
// Container for extruder overrides (when wiping into object or infill). // Container for extruder overrides (when wiping into object or infill).
const LayerTools &layer_tools, const LayerTools &layer_tools,
// Optional smooth path interpolating extrusion polylines. // Optional smooth path interpolating extrusion polylines.
const GCode::SmoothPathCache *smooth_path_cache, const GCode::SmoothPathCache &smooth_path_cache,
// Is any extrusion possibly marked as wiping extrusion? // Is any extrusion possibly marked as wiping extrusion?
const bool is_anything_overridden, const bool is_anything_overridden,
// Round 1 (wiping into object or infill) or round 2 (normal extrusions). // Round 1 (wiping into object or infill) or round 2 (normal extrusions).
@ -2413,7 +2437,19 @@ std::string GCodeGenerator::change_layer(coordf_t print_z)
return gcode; return gcode;
} }
std::string GCodeGenerator::extrude_loop(const ExtrusionLoop &loop_src, const GCode::SmoothPathCache *smooth_path_cache, const std::string_view description, double speed) #ifndef NDEBUG
static inline bool validate_smooth_path(const GCode::SmoothPath &smooth_path, bool loop)
{
for (auto it = std::next(smooth_path.begin()); it != smooth_path.end(); ++ it) {
assert(it->path.size() >= 2);
assert(std::prev(it)->path.back().point == it->path.front().point);
}
assert(! loop || smooth_path.front().path.front().point == smooth_path.back().path.back().point);
return true;
}
#endif //NDEBUG
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.
bool is_hole = loop_src.is_clockwise(); bool is_hole = loop_src.is_clockwise();
@ -2424,7 +2460,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, is_hole, 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;
@ -2436,13 +2472,7 @@ std::string GCodeGenerator::extrude_loop(const ExtrusionLoop &loop_src, const GC
if (smooth_path.empty()) if (smooth_path.empty())
return {}; return {};
#ifndef NDEBUG assert(validate_smooth_path(smooth_path, ! m_enable_loop_clipping));
for (auto it = std::next(smooth_path.begin()); it != smooth_path.end(); ++ it) {
assert(it->path.size() >= 2);
assert(std::prev(it)->path.back().point == it->path.front().point);
}
assert(m_enable_loop_clipping || smooth_path.front().path.front().point == smooth_path.back().path.back().point);
#endif //NDEBUG
// Apply the small perimeter speed. // Apply the small perimeter speed.
if (loop_src.paths.front().role().is_perimeter() && loop_src.length() <= SMALL_PERIMETER_LENGTH && speed == -1) if (loop_src.paths.front().role().is_perimeter() && loop_src.length() <= SMALL_PERIMETER_LENGTH && speed == -1)
@ -2470,7 +2500,45 @@ std::string GCodeGenerator::extrude_loop(const ExtrusionLoop &loop_src, const GC
return gcode; return gcode;
} }
std::string GCodeGenerator::extrude_multi_path(const ExtrusionMultiPath &multipath, bool reverse, const GCode::SmoothPathCache *smooth_path_cache, const std::string_view description, double speed) std::string GCodeGenerator::extrude_skirt(
const ExtrusionLoop &loop_src, const ExtrusionFlow &extrusion_flow_override,
const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed)
{
assert(loop_src.is_counter_clockwise());
GCode::SmoothPath smooth_path = smooth_path_cache.resolve_or_fit_split_with_seam(
loop_src, false, m_scaled_resolution, this->last_pos(), 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
// we discard it in that case.
if (m_enable_loop_clipping)
clip_end(smooth_path, scale_(EXTRUDER_CONFIG(nozzle_diameter)) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER);
if (smooth_path.empty())
return {};
assert(validate_smooth_path(smooth_path, ! m_enable_loop_clipping));
// Extrude along the smooth path.
std::string gcode;
for (GCode::SmoothPathElement &el : smooth_path) {
// Override extrusion parameters.
el.path_attributes.mm3_per_mm = extrusion_flow_override.mm3_per_mm;
el.path_attributes.height = extrusion_flow_override.height;
gcode += this->_extrude(el.path_attributes, el.path, description, speed);
}
// reset acceleration
gcode += m_writer.set_print_acceleration(fast_round_up<unsigned int>(m_config.default_acceleration.value));
if (m_wipe.enabled())
// Wipe will hide the seam.
m_wipe.set_path(std::move(smooth_path), false);
return gcode;
}
std::string GCodeGenerator::extrude_multi_path(const ExtrusionMultiPath &multipath, bool reverse, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed)
{ {
#ifndef NDEBUG #ifndef NDEBUG
for (auto it = std::next(multipath.paths.begin()); it != multipath.paths.end(); ++ it) { for (auto it = std::next(multipath.paths.begin()); it != multipath.paths.end(); ++ it) {
@ -2478,7 +2546,7 @@ std::string GCodeGenerator::extrude_multi_path(const ExtrusionMultiPath &multipa
assert(std::prev(it)->polyline.last_point() == it->polyline.first_point()); assert(std::prev(it)->polyline.last_point() == it->polyline.first_point());
} }
#endif // NDEBUG #endif // NDEBUG
GCode::SmoothPath smooth_path = smooth_path_cache->resolve_or_fit(multipath, reverse, m_scaled_resolution); GCode::SmoothPath smooth_path = smooth_path_cache.resolve_or_fit(multipath, reverse, m_scaled_resolution);
// extrude along the path // extrude along the path
std::string gcode; std::string gcode;
@ -2490,7 +2558,7 @@ std::string GCodeGenerator::extrude_multi_path(const ExtrusionMultiPath &multipa
return gcode; return gcode;
} }
std::string GCodeGenerator::extrude_entity(const ExtrusionEntityReference &entity, const GCode::SmoothPathCache *smooth_path_cache, const std::string_view description, double speed) std::string GCodeGenerator::extrude_entity(const ExtrusionEntityReference &entity, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed)
{ {
if (const ExtrusionPath *path = dynamic_cast<const ExtrusionPath*>(&entity.extrusion_entity())) if (const ExtrusionPath *path = dynamic_cast<const ExtrusionPath*>(&entity.extrusion_entity()))
return this->extrude_path(*path, entity.flipped(), smooth_path_cache, description, speed); return this->extrude_path(*path, entity.flipped(), smooth_path_cache, description, speed);
@ -2503,9 +2571,9 @@ std::string GCodeGenerator::extrude_entity(const ExtrusionEntityReference &entit
return {}; return {};
} }
std::string GCodeGenerator::extrude_path(const ExtrusionPath &path, bool reverse, const GCode::SmoothPathCache *smooth_path_cache, std::string_view description, double speed) std::string GCodeGenerator::extrude_path(const ExtrusionPath &path, bool reverse, const GCode::SmoothPathCache &smooth_path_cache, std::string_view description, double speed)
{ {
Geometry::ArcWelder::Path smooth_path = smooth_path_cache->resolve_or_fit(path, reverse, m_scaled_resolution); Geometry::ArcWelder::Path smooth_path = smooth_path_cache.resolve_or_fit(path, reverse, m_scaled_resolution);
std::string gcode = this->_extrude(path.attributes(), smooth_path, description, speed); std::string gcode = this->_extrude(path.attributes(), smooth_path, description, speed);
Geometry::ArcWelder::reverse(smooth_path); Geometry::ArcWelder::reverse(smooth_path);
m_wipe.set_path(std::move(smooth_path)); m_wipe.set_path(std::move(smooth_path));
@ -2514,7 +2582,7 @@ std::string GCodeGenerator::extrude_path(const ExtrusionPath &path, bool reverse
return gcode; return gcode;
} }
std::string GCodeGenerator::extrude_support(const ExtrusionEntityReferences &support_fills, const GCode::SmoothPathCache *smooth_path_cache) std::string GCodeGenerator::extrude_support(const ExtrusionEntityReferences &support_fills, const GCode::SmoothPathCache &smooth_path_cache)
{ {
static constexpr const auto support_label = "support material"sv; static constexpr const auto support_label = "support material"sv;
static constexpr const auto support_interface_label = "support material interface"sv; static constexpr const auto support_interface_label = "support material interface"sv;

View File

@ -202,7 +202,7 @@ private:
// Set of object & print layers of the same PrintObject and with the same print_z. // Set of object & print layers of the same PrintObject and with the same print_z.
const ObjectsLayerToPrint &layers, const ObjectsLayerToPrint &layers,
const LayerTools &layer_tools, const LayerTools &layer_tools,
const GCode::SmoothPathCache *smooth_path_cache, const GCode::SmoothPathCaches &smooth_path_caches,
const bool last_layer, const bool last_layer,
// Pairs of PrintObject index and its instance index. // Pairs of PrintObject index and its instance index.
const std::vector<const PrintInstance*> *ordering, const std::vector<const PrintInstance*> *ordering,
@ -217,6 +217,7 @@ private:
const ToolOrdering &tool_ordering, const ToolOrdering &tool_ordering,
const std::vector<const PrintInstance*> &print_object_instances_ordering, const std::vector<const PrintInstance*> &print_object_instances_ordering,
const std::vector<std::pair<coordf_t, ObjectsLayerToPrint>> &layers_to_print, const std::vector<std::pair<coordf_t, ObjectsLayerToPrint>> &layers_to_print,
const GCode::SmoothPathCache &smooth_path_cache_global,
GCodeOutputStream &output_stream); GCodeOutputStream &output_stream);
// Process all layers of a single object instance (sequential mode) with a parallel pipeline: // Process all layers of a single object instance (sequential mode) with a parallel pipeline:
// Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser
@ -226,6 +227,7 @@ private:
const ToolOrdering &tool_ordering, const ToolOrdering &tool_ordering,
ObjectsLayerToPrint layers_to_print, ObjectsLayerToPrint layers_to_print,
const size_t single_object_idx, const size_t single_object_idx,
const GCode::SmoothPathCache &smooth_path_cache_global,
GCodeOutputStream &output_stream); GCodeOutputStream &output_stream);
void set_last_pos(const Point &pos) { m_last_pos = pos; m_last_pos_defined = true; } void set_last_pos(const Point &pos) { m_last_pos = pos; m_last_pos_defined = true; }
@ -233,10 +235,13 @@ private:
void set_extruders(const std::vector<unsigned int> &extruder_ids); void set_extruders(const std::vector<unsigned int> &extruder_ids);
std::string preamble(); std::string preamble();
std::string change_layer(coordf_t print_z); std::string change_layer(coordf_t print_z);
std::string extrude_entity(const ExtrusionEntityReference &entity, const GCode::SmoothPathCache *smooth_path_cache, const std::string_view description, double speed = -1.); std::string extrude_entity(const ExtrusionEntityReference &entity, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed = -1.);
std::string extrude_loop(const ExtrusionLoop &loop, const GCode::SmoothPathCache *smooth_path_cache, const std::string_view description, double speed = -1.); std::string extrude_loop(const ExtrusionLoop &loop, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed = -1.);
std::string extrude_multi_path(const ExtrusionMultiPath &multipath, bool reverse, const GCode::SmoothPathCache *smooth_path_cache, const std::string_view description, double speed = -1.); std::string extrude_skirt(const ExtrusionLoop &loop_src, const ExtrusionFlow &extrusion_flow_override,
std::string extrude_path(const ExtrusionPath &path, bool reverse, const GCode::SmoothPathCache *smooth_path_cache, const std::string_view description, double speed = -1.); const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed);
std::string extrude_multi_path(const ExtrusionMultiPath &multipath, bool reverse, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed = -1.);
std::string extrude_path(const ExtrusionPath &path, bool reverse, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed = -1.);
struct InstanceToPrint struct InstanceToPrint
{ {
@ -271,13 +276,13 @@ private:
// Container for extruder overrides (when wiping into object or infill). // Container for extruder overrides (when wiping into object or infill).
const LayerTools &layer_tools, const LayerTools &layer_tools,
// Optional smooth path interpolating extrusion polylines. // Optional smooth path interpolating extrusion polylines.
const GCode::SmoothPathCache *smooth_path_cache, const GCode::SmoothPathCache &smooth_path_cache,
// Is any extrusion possibly marked as wiping extrusion? // Is any extrusion possibly marked as wiping extrusion?
const bool is_anything_overridden, const bool is_anything_overridden,
// Round 1 (wiping into object or infill) or round 2 (normal extrusions). // Round 1 (wiping into object or infill) or round 2 (normal extrusions).
const bool print_wipe_extrusions); const bool print_wipe_extrusions);
std::string extrude_support(const ExtrusionEntityReferences &support_fills, const GCode::SmoothPathCache *smooth_path_cache); std::string extrude_support(const ExtrusionEntityReferences &support_fills, const GCode::SmoothPathCache &smooth_path_cache);
std::string travel_to(const Point &point, ExtrusionRole role, std::string comment); std::string travel_to(const Point &point, ExtrusionRole role, std::string comment);
bool needs_retraction(const Polyline &travel, ExtrusionRole role = ExtrusionRole::None); bool needs_retraction(const Polyline &travel, ExtrusionRole role = ExtrusionRole::None);

View File

@ -121,26 +121,32 @@ double clip_end(SmoothPath &path, double distance)
return distance; return distance;
} }
void SmoothPathCache::interpolate_add(const Polyline &polyline, const InterpolationParameters &params)
{
m_cache[&polyline] = Slic3r::Geometry::ArcWelder::fit_path(polyline.points, params.tolerance, params.fit_circle_tolerance);
}
void SmoothPathCache::interpolate_add(const ExtrusionPath &path, const InterpolationParameters &params) void SmoothPathCache::interpolate_add(const ExtrusionPath &path, const InterpolationParameters &params)
{ {
this->interpolate_add(path.polyline, params); double tolerance = params.tolerance;
if (path.role().is_sparse_infill())
// Use 3x lower resolution than the object fine detail for sparse infill.
tolerance *= 3.;
else if (path.role().is_support())
// Use 4x lower resolution than the object fine detail for support.
tolerance *= 4.;
else if (path.role().is_skirt())
// Brim is currently marked as skirt.
// Use 4x lower resolution than the object fine detail for skirt & brim.
tolerance *= 4.;
m_cache[&path.polyline] = Slic3r::Geometry::ArcWelder::fit_path(path.polyline.points, tolerance, params.fit_circle_tolerance);
} }
void SmoothPathCache::interpolate_add(const ExtrusionMultiPath &multi_path, const InterpolationParameters &params) void SmoothPathCache::interpolate_add(const ExtrusionMultiPath &multi_path, const InterpolationParameters &params)
{ {
for (const ExtrusionPath &path : multi_path.paths) for (const ExtrusionPath &path : multi_path.paths)
this->interpolate_add(path.polyline, params); this->interpolate_add(path, params);
} }
void SmoothPathCache::interpolate_add(const ExtrusionLoop &loop, const InterpolationParameters &params) void SmoothPathCache::interpolate_add(const ExtrusionLoop &loop, const InterpolationParameters &params)
{ {
for (const ExtrusionPath &path : loop.paths) for (const ExtrusionPath &path : loop.paths)
this->interpolate_add(path.polyline, params); this->interpolate_add(path, params);
} }
void SmoothPathCache::interpolate_add(const ExtrusionEntityCollection &eec, const InterpolationParameters &params) void SmoothPathCache::interpolate_add(const ExtrusionEntityCollection &eec, const InterpolationParameters &params)
@ -219,6 +225,9 @@ SmoothPath SmoothPathCache::resolve_or_fit_split_with_seam(
assert(this_proj.segment_id >= 0 && this_proj.segment_id < el.path.size()); assert(this_proj.segment_id >= 0 && this_proj.segment_id < el.path.size());
proj = this_proj; proj = this_proj;
proj_path = &el - out.data(); proj_path = &el - out.data();
if (proj.distance2 == 0)
// There will be no better split point found than one with zero distance.
break;
} }
assert(proj_path >= 0); assert(proj_path >= 0);
// Split the path at the closest point. // Split the path at the closest point.

View File

@ -39,7 +39,6 @@ public:
double fit_circle_tolerance; double fit_circle_tolerance;
}; };
void interpolate_add(const Polyline &pl, const InterpolationParameters &params);
void interpolate_add(const ExtrusionPath &ee, const InterpolationParameters &params); void interpolate_add(const ExtrusionPath &ee, const InterpolationParameters &params);
void interpolate_add(const ExtrusionMultiPath &ee, const InterpolationParameters &params); void interpolate_add(const ExtrusionMultiPath &ee, const InterpolationParameters &params);
void interpolate_add(const ExtrusionLoop &ee, const InterpolationParameters &params); void interpolate_add(const ExtrusionLoop &ee, const InterpolationParameters &params);
@ -64,6 +63,24 @@ private:
ankerl::unordered_dense::map<const Polyline*, Geometry::ArcWelder::Path> m_cache; ankerl::unordered_dense::map<const Polyline*, Geometry::ArcWelder::Path> m_cache;
}; };
// Encapsulates references to global and layer local caches of smooth extrusion paths.
class SmoothPathCaches final
{
public:
SmoothPathCaches() = delete;
SmoothPathCaches(const SmoothPathCache &global, const SmoothPathCache &layer_local) :
m_global(&global), m_layer_local(&layer_local) {}
SmoothPathCaches operator=(const SmoothPathCaches &rhs)
{ m_global = rhs.m_global; m_layer_local = rhs.m_layer_local; return *this; }
const SmoothPathCache& global() const { return *m_global; }
const SmoothPathCache& layer_local() const { return *m_layer_local; }
private:
const SmoothPathCache *m_global;
const SmoothPathCache *m_layer_local;
};
} // namespace GCode } // namespace GCode
} // namespace Slic3r } // namespace Slic3r

View File

@ -590,6 +590,7 @@ std::pair<Path, Path> split_at(const Path &path, const PathSegmentProjection &pr
{ {
assert(proj.valid()); assert(proj.valid());
assert(! proj.valid() || (proj.segment_id >= 0 && proj.segment_id < path.size())); assert(! proj.valid() || (proj.segment_id >= 0 && proj.segment_id < path.size()));
assert(path.size() > 1);
std::pair<Path, Path> out; std::pair<Path, Path> out;
if (! proj.valid() || proj.segment_id + 1 == path.size() || (proj.segment_id + 2 == path.size() && proj.point == path.back().point)) if (! proj.valid() || proj.segment_id + 1 == path.size() || (proj.segment_id + 2 == path.size() && proj.point == path.back().point))
out.first = path; out.first = path;
@ -637,18 +638,28 @@ std::pair<Path, Path> split_at(const Path &path, const PathSegmentProjection &pr
if ((cross2(vproj, vend) > 0) == end.ccw()) if ((cross2(vproj, vend) > 0) == end.ccw())
// Make the radius of a minor arc positive. // Make the radius of a minor arc positive.
out.second[1].radius *= -1.f; out.second[1].radius *= -1.f;
assert(out.first.size() > 1);
assert(out.second.size() > 1);
out.second.front().radius = 0;
} }
} else { } else {
// Split at the start of proj.segment_id. assert(split_segment_id >= 0 && split_segment_id < path.size());
out.first.assign(path.begin(), path.begin() + split_segment_id + 1); if (split_segment_id + 1 == path.size())
out.second.assign(path.begin() + split_segment_id, path.end()); out.first = path;
assert(out.first.size() + out.second.size() == path.size() + 1); else if (split_segment_id == 0)
assert(out.first.back() == (split_segment_id == proj.segment_id ? start : end)); out.second = path;
assert(out.second.front() == (split_segment_id == proj.segment_id ? start : end)); else {
// Split at the start of proj.segment_id.
out.first.assign(path.begin(), path.begin() + split_segment_id + 1);
out.second.assign(path.begin() + split_segment_id, path.end());
assert(out.first.size() + out.second.size() == path.size() + 1);
assert(out.first.back() == (split_segment_id == proj.segment_id ? start : end));
assert(out.second.front() == (split_segment_id == proj.segment_id ? start : end));
assert(out.first.size() > 1);
assert(out.second.size() > 1);
out.second.front().radius = 0;
}
} }
assert(out.first.size() > 1);
assert(out.second.size() > 1);
out.second.front().radius = 0;
} }
return out; return out;