From aa6132ebb6284168f83cabf38ed6a3db1c3eadcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ach?= Date: Thu, 11 Jul 2024 09:49:14 +0200 Subject: [PATCH] Scarf seam implementation. --- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/GCode.cpp | 170 +++++++--- src/libslic3r/GCode.hpp | 14 +- src/libslic3r/GCode/ExtrusionOrder.cpp | 47 +-- src/libslic3r/GCode/ExtrusionOrder.hpp | 8 +- src/libslic3r/GCode/GCodeWriter.cpp | 13 +- src/libslic3r/GCode/GCodeWriter.hpp | 5 + src/libslic3r/GCode/SeamAligned.cpp | 8 +- src/libslic3r/GCode/SeamGeometry.cpp | 69 +++- src/libslic3r/GCode/SeamGeometry.hpp | 22 +- src/libslic3r/GCode/SeamPerimeters.cpp | 4 +- src/libslic3r/GCode/SeamPlacer.cpp | 166 +++++---- src/libslic3r/GCode/SeamPlacer.hpp | 7 +- src/libslic3r/GCode/SeamScarf.cpp | 334 +++++++++++++++++++ src/libslic3r/GCode/SeamScarf.hpp | 85 +++++ src/libslic3r/GCode/SmoothPath.cpp | 10 +- src/libslic3r/GCode/SmoothPath.hpp | 5 +- src/libslic3r/GCode/Travels.cpp | 1 - src/libslic3r/GCode/WipeTowerIntegration.cpp | 7 +- src/libslic3r/Geometry/ArcWelder.cpp | 2 - src/libslic3r/Geometry/ArcWelder.hpp | 3 + tests/fff_print/CMakeLists.txt | 1 + tests/fff_print/benchmark_seams.cpp | 9 +- tests/fff_print/test_seam_geometry.cpp | 27 ++ tests/fff_print/test_seam_scarf.cpp | 295 ++++++++++++++++ 25 files changed, 1151 insertions(+), 163 deletions(-) create mode 100644 src/libslic3r/GCode/SeamScarf.cpp create mode 100644 src/libslic3r/GCode/SeamScarf.hpp create mode 100644 tests/fff_print/test_seam_scarf.cpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 937d4db91c..ff22f71ab9 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -195,6 +195,8 @@ set(SLIC3R_SOURCES GCode/SeamRandom.hpp GCode/SeamPainting.cpp GCode/SeamPainting.hpp + GCode/SeamScarf.cpp + GCode/SeamScarf.hpp GCode/ModelVisibility.cpp GCode/ModelVisibility.hpp GCode/SmoothPath.cpp diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index d1323c8b31..06cf3278bf 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1273,7 +1273,9 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail file.write(m_label_objects.maybe_stop_instance()); const double last_z{this->writer().get_position().z()}; file.write(this->writer().get_travel_to_z_gcode(last_z, "ensure z position")); - file.write(this->travel_to(*this->last_position, Point(0, 0), ExtrusionRole::None, "move to origin position for next object", [](){return "";})); + const Vec3crd from{to_3d(*this->last_position, scaled(this->m_last_layer_z))}; + const Vec3crd to{0, 0, scaled(this->m_last_layer_z)}; + file.write(this->travel_to(from, to, ExtrusionRole::None, "move to origin position for next object", [](){return "";})); m_enable_cooling_markers = true; // Disable motion planner when traveling to first object point. m_avoid_crossing_perimeters.disable_once(); @@ -2267,6 +2269,8 @@ std::string GCodeGenerator::generate_ramping_layer_change_gcode( #ifndef NDEBUG static inline bool validate_smooth_path(const GCode::SmoothPath &smooth_path, bool loop) { + assert(!smooth_path.empty()); + 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); @@ -2276,33 +2280,86 @@ static inline bool validate_smooth_path(const GCode::SmoothPath &smooth_path, bo } #endif //NDEBUG +namespace GCode { + +std::pair split_with_seam( + const ExtrusionLoop &loop, + const std::variant &seam, + const bool flipped, + const GCode::SmoothPathCache &smooth_path_cache, + const double scaled_resolution, + const double seam_point_merge_distance_threshold +) { + if (loop.paths.empty() || loop.paths.front().empty()) { + return {SmoothPath{}, 0}; + } + if (std::holds_alternative(seam)) { + const auto seam_point = std::get(seam); + + return { + smooth_path_cache.resolve_or_fit_split_with_seam( + loop, flipped, scaled_resolution, seam_point, seam_point_merge_distance_threshold + ), + 0}; + } else if (std::holds_alternative(seam)) { + const auto scarf{std::get(seam)}; + ExtrusionPaths paths{loop.paths}; + const auto apply_smoothing{[&](tcb::span paths){ + return smooth_path_cache.resolve_or_fit(paths, false, scaled(0.0015)); + }}; + return Seams::Scarf::add_scarf_seam(std::move(paths), scarf, apply_smoothing, flipped); + } else { + throw std::runtime_error{"Unknown seam type!"}; + } +} +} // namespace GCode + using GCode::ExtrusionOrder::InstancePoint; -struct SmoothPathGenerator { +struct SmoothPathGenerator +{ const Seams::Placer &seam_placer; const GCode::SmoothPathCaches &smooth_path_caches; double scaled_resolution; const PrintConfig &config; bool enable_loop_clipping; - GCode::SmoothPath operator()(const Layer *layer, const ExtrusionEntityReference &extrusion_reference, const unsigned extruder_id, std::optional &previous_position) { + GCode::ExtrusionOrder::PathSmoothingResult operator()( + const Layer *layer, + const PrintRegion *region, + const ExtrusionEntityReference &extrusion_reference, + const unsigned extruder_id, + std::optional &previous_position + ) { const ExtrusionEntity *extrusion_entity{&extrusion_reference.extrusion_entity()}; GCode::SmoothPath result; + std::size_t wipe_offset{0}; if (auto loop = dynamic_cast(extrusion_entity)) { - Point seam_point = previous_position ? previous_position->local_point : Point::Zero(); - if (!config.spiral_vase && loop->role().is_perimeter() && layer != nullptr) { - seam_point = this->seam_placer.place_seam(layer, *loop, seam_point); - } - - const GCode::SmoothPathCache &smooth_path_cache{loop->role().is_perimeter() ? smooth_path_caches.layer_local() : smooth_path_caches.global()}; // 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, extrusion_reference.flipped(), scaled_resolution, seam_point, scaled(0.0015) + const auto seam_point_merge_distance_threshold{scaled(0.0015)}; + const GCode::SmoothPathCache &smooth_path_cache{ + loop->role().is_perimeter() ? smooth_path_caches.layer_local() : + smooth_path_caches.global()}; + const Point previous_point{ + previous_position ? previous_position->local_point : Point::Zero()}; + + if (!config.spiral_vase && loop->role().is_perimeter() && layer != nullptr && region != nullptr) { + std::variant seam{ + this->seam_placer + .place_seam(layer, region, *loop, extrusion_reference.flipped(), previous_point)}; + std::tie(result, wipe_offset) = split_with_seam( + *loop, seam, extrusion_reference.flipped(), smooth_path_cache, + scaled_resolution, seam_point_merge_distance_threshold ); + } else { + result = smooth_path_cache.resolve_or_fit_split_with_seam( + *loop, extrusion_reference.flipped(), scaled_resolution, previous_point, + seam_point_merge_distance_threshold + ); + } // 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 @@ -2310,19 +2367,21 @@ struct SmoothPathGenerator { const auto nozzle_diameter{config.nozzle_diameter.get_at(extruder_id)}; if (enable_loop_clipping) { clip_end( - smooth_path, - scale_(nozzle_diameter) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER, + result, scale_(nozzle_diameter) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER, scaled(GCode::ExtrusionOrder::min_gcode_segment_length) ); } - assert(validate_smooth_path(smooth_path, !enable_loop_clipping)); - - result = smooth_path; + assert(validate_smooth_path(result, !enable_loop_clipping)); } else if (auto multipath = dynamic_cast(extrusion_entity)) { - result = smooth_path_caches.layer_local().resolve_or_fit(*multipath, extrusion_reference.flipped(), scaled_resolution); + result = + smooth_path_caches.layer_local() + .resolve_or_fit(*multipath, extrusion_reference.flipped(), scaled_resolution); } else if (auto path = dynamic_cast(extrusion_entity)) { - result = GCode::SmoothPath{GCode::SmoothPathElement{path->attributes(), smooth_path_caches.layer_local().resolve_or_fit(*path, extrusion_reference.flipped(), scaled_resolution)}}; + result = GCode::SmoothPath{GCode::SmoothPathElement{ + path->attributes(), + smooth_path_caches.layer_local() + .resolve_or_fit(*path, extrusion_reference.flipped(), scaled_resolution)}}; } for (auto it{result.rbegin()}; it != result.rend(); ++it) { if (!it->path.empty()) { @@ -2331,9 +2390,8 @@ struct SmoothPathGenerator { } } - return result; + return {result, wipe_offset}; } - }; std::vector GCodeGenerator::get_sorted_extrusions( @@ -2458,6 +2516,8 @@ LayerResult GCodeGenerator::process_layer( m_enable_loop_clipping = !enable; } + const float height = first_layer ? static_cast(print_z) : static_cast(print_z) - m_last_layer_z; + using GCode::ExtrusionOrder::ExtruderExtrusions; const std::vector extrusions{ this->get_sorted_extrusions(print, layers, layer_tools, instances_to_print, smooth_path_caches, first_layer)}; @@ -2465,7 +2525,8 @@ LayerResult GCodeGenerator::process_layer( if (extrusions.empty()) { return result; } - const Point first_point{*GCode::ExtrusionOrder::get_first_point(extrusions)}; + const Geometry::ArcWelder::Segment first_segment{*GCode::ExtrusionOrder::get_first_point(extrusions)}; + const Vec3crd first_point{to_3d(first_segment.point, scaled(print_z + (first_segment.height_fraction - 1.0) * height))}; const PrintInstance* first_instance{get_first_instance(extrusions, instances_to_print)}; m_label_objects.update(first_instance); @@ -2479,7 +2540,6 @@ LayerResult GCodeGenerator::process_layer( gcode += std::string(";Z:") + float_to_string_decimal_point(print_z) + "\n"; // export layer height - float height = first_layer ? static_cast(print_z) : static_cast(print_z) - m_last_layer_z; gcode += std::string(";") + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height) + float_to_string_decimal_point(height) + "\n"; @@ -2499,7 +2559,7 @@ LayerResult GCodeGenerator::process_layer( print.config().before_layer_gcode.value, m_writer.extruder()->id(), &config) + "\n"; } - gcode += this->change_layer(previous_layer_z, print_z, result.spiral_vase_enable, first_point, first_layer); // this will increase m_layer_index + gcode += this->change_layer(previous_layer_z, print_z, result.spiral_vase_enable, first_point.head<2>(), first_layer); // this will increase m_layer_index m_layer = &layer; if (this->line_distancer_is_required(layer_tools.extruders) && this->m_layer != nullptr && this->m_layer->lower_layer != nullptr) m_travel_obstacle_tracker.init_layer(layer, layers); @@ -2590,9 +2650,7 @@ LayerResult GCodeGenerator::process_layer( } if (!this->m_moved_to_first_layer_point) { - const Vec3crd point{to_3d(first_point, scaled(print_z))}; - - gcode += this->travel_to_first_position(point, print_z, ExtrusionRole::Mixed, [this]() { + gcode += this->travel_to_first_position(first_point, print_z, ExtrusionRole::Mixed, [this]() { if (m_writer.multiple_extruders) { return std::string{""}; } @@ -2875,7 +2933,11 @@ std::string GCodeGenerator::change_layer( } std::string GCodeGenerator::extrude_smooth_path( - const GCode::SmoothPath &smooth_path, const bool is_loop, const std::string_view description, const double speed + const GCode::SmoothPath &smooth_path, + const bool is_loop, + const std::string_view description, + const double speed, + const std::size_t wipe_offset ) { std::string gcode; @@ -2913,8 +2975,13 @@ std::string GCodeGenerator::extrude_smooth_path( gcode += m_writer.set_print_acceleration(fast_round_up(m_config.default_acceleration.value)); if (is_loop) { - m_wipe.set_path(GCode::SmoothPath{smooth_path}); + GCode::SmoothPath wipe{smooth_path.begin() + wipe_offset, smooth_path.end()}; + m_wipe.set_path(std::move(wipe)); } else { + if (wipe_offset > 0) { + throw std::runtime_error("Wipe offset is not supported for non looped paths!"); + } + GCode::SmoothPath reversed_smooth_path{smooth_path}; GCode::reverse(reversed_smooth_path); m_wipe.set_path(std::move(reversed_smooth_path)); @@ -2971,7 +3038,7 @@ std::string GCodeGenerator::extrude_perimeters( // Apply the small perimeter speed. if (perimeter.extrusion_entity->length() <= SMALL_PERIMETER_LENGTH) speed = m_config.small_perimeter_speed.get_abs_value(m_config.perimeter_speed); - gcode += this->extrude_smooth_path(perimeter.smooth_path, true, comment_perimeter, speed); + gcode += this->extrude_smooth_path(perimeter.smooth_path, true, comment_perimeter, speed, perimeter.wipe_offset); this->m_travel_obstacle_tracker.mark_extruded( perimeter.extrusion_entity, print_instance.object_layer_to_print_id, print_instance.instance_id ); @@ -3083,8 +3150,9 @@ std::string GCodeGenerator::travel_to_first_position(const Vec3crd& point, const Vec3d writer_position{this->writer().get_position()}; writer_position.z() = 0.0; // Endofrce z generation! this->writer().update_position(writer_position); + const Vec3crd from{to_3d(*this->last_position, scaled(from_z))}; gcode = this->travel_to( - *this->last_position, point.head<2>(), role, "travel to first layer point", insert_gcode + from, point, role, "travel to first layer point", insert_gcode ); } else { double lift{ @@ -3170,7 +3238,9 @@ std::string GCodeGenerator::_extrude( comment += description; comment += description_bridge; comment += " point"; - const std::string travel_gcode{this->travel_to(*this->last_position, path.front().point, path_attr.role, comment, [this](){ + const Vec3crd from{to_3d(*this->last_position, scaled(this->m_last_layer_z))}; + const Vec3crd to{to_3d(path.front().point, scaled(this->m_last_layer_z + (path.front().height_fraction - 1.0) * path_attr.height))}; + const std::string travel_gcode{this->travel_to(from, to, path_attr.role, comment, [this](){ return m_writer.multiple_extruders ? "" : m_label_objects.maybe_change_instance(m_writer); })}; gcode += travel_gcode; @@ -3352,7 +3422,7 @@ std::string GCodeGenerator::_extrude( for (++ it; it != end; ++ it) { Vec2d p_exact = this->point_to_gcode(it->point); Vec2d p = GCodeFormatter::quantize(p_exact); - assert(p != prev); + //assert(p != prev); if (p != prev) { // Center of the radius to be emitted into the G-code: Either by radius or by center offset. double radius = 0; @@ -3373,8 +3443,15 @@ std::string GCodeGenerator::_extrude( } if (radius == 0) { // Extrude line segment. - if (const double line_length = (p - prev).norm(); line_length > 0) - gcode += m_writer.extrude_to_xy(p, e_per_mm * line_length, comment); + if (const double line_length = (p - prev).norm(); line_length > 0) { + double extrusion_amount{e_per_mm * line_length * it->e_fraction}; + if (it->height_fraction < 1.0 || std::prev(it)->height_fraction < 1.0) { + const Vec3d destination{to_3d(p, this->m_last_layer_z + (it->height_fraction - 1) * m_last_height)}; + gcode += m_writer.extrude_to_xyz(destination, extrusion_amount); + } else { + gcode += m_writer.extrude_to_xy(p, extrusion_amount, comment); + } + } } else { double angle = Geometry::ArcWelder::arc_angle(prev.cast(), p.cast(), double(radius)); assert(angle > 0); @@ -3528,19 +3605,21 @@ Polyline GCodeGenerator::generate_travel_xy_path( // This method accepts &point in print coordinates. std::string GCodeGenerator::travel_to( - const Point &start_point, - const Point &end_point, + const Vec3crd &start_point, + const Vec3crd &end_point, ExtrusionRole role, const std::string &comment, const std::function& insert_gcode ) { + const double initial_elevation{unscaled(start_point.z())}; + // check whether a straight travel move would need retraction bool could_be_wipe_disabled {false}; - bool needs_retraction = this->needs_retraction(Polyline{start_point, end_point}, role); + bool needs_retraction = this->needs_retraction(Polyline{start_point.head<2>(), end_point.head<2>()}, role); Polyline xy_path{generate_travel_xy_path( - start_point, end_point, needs_retraction, could_be_wipe_disabled + start_point.head<2>(), end_point.head<2>(), needs_retraction, could_be_wipe_disabled )}; needs_retraction = this->needs_retraction(xy_path, role); @@ -3556,7 +3635,7 @@ std::string GCodeGenerator::travel_to( if (*this->last_position != position_before_wipe) { xy_path = generate_travel_xy_path( - *this->last_position, end_point, needs_retraction, could_be_wipe_disabled + *this->last_position, end_point.head<2>(), needs_retraction, could_be_wipe_disabled ); } } else { @@ -3568,7 +3647,6 @@ std::string GCodeGenerator::travel_to( const unsigned extruder_id = this->m_writer.extruder()->id(); const double retract_length = this->m_config.retract_length.get_at(extruder_id); bool can_be_flat{!needs_retraction || retract_length == 0}; - const double initial_elevation = this->m_last_layer_z; const double upper_limit = this->m_config.retract_lift_below.get_at(extruder_id); const double lower_limit = this->m_config.retract_lift_above.get_at(extruder_id); @@ -3577,7 +3655,7 @@ std::string GCodeGenerator::travel_to( can_be_flat = true; } - const Points3 travel = ( + Points3 travel = ( can_be_flat ? GCode::Impl::Travels::generate_flat_travel(xy_path.points, initial_elevation) : GCode::Impl::Travels::generate_travel_to_extrusion( @@ -3589,6 +3667,14 @@ std::string GCodeGenerator::travel_to( scaled(m_origin) ) ); + if (this->config().scarf_seam_placement != ScarfSeamPlacement::nowhere && + role == ExtrusionRole::ExternalPerimeter && can_be_flat && travel.size() == 2 && + scaled(2.0) > xy_path.length()) { + + // Go directly to the outter perimeter. + travel.pop_back(); + } + travel.emplace_back(end_point); return wipe_retract_gcode + generate_travel_gcode(travel, comment, insert_gcode); } diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 0727f9071f..bd8e898448 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -284,9 +284,15 @@ private: const bool first_layer ); std::string extrude_smooth_path( - const GCode::SmoothPath &smooth_path, const bool is_loop, const std::string_view description, const double speed + const GCode::SmoothPath &smooth_path, + const bool is_loop, + const std::string_view description, + const double speed, + const std::size_t wipe_offset = 0 + ); + std::string extrude_skirt( + GCode::SmoothPath smooth_path, const ExtrusionFlow &extrusion_flow_override ); - std::string extrude_skirt(GCode::SmoothPath smooth_path, const ExtrusionFlow &extrusion_flow_override); std::vector sort_print_object_instances( // Object and Support layers for the current print_z, collected for a single object, or for possibly multiple objects with multiple instances. @@ -334,8 +340,8 @@ private: bool& could_be_wipe_disabled ); std::string travel_to( - const Point &start_point, - const Point &end_point, + const Vec3crd &start_point, + const Vec3crd &end_point, ExtrusionRole role, const std::string &comment, const std::function& insert_gcode diff --git a/src/libslic3r/GCode/ExtrusionOrder.cpp b/src/libslic3r/GCode/ExtrusionOrder.cpp index 713bac78b4..b1fdc1de8c 100644 --- a/src/libslic3r/GCode/ExtrusionOrder.cpp +++ b/src/libslic3r/GCode/ExtrusionOrder.cpp @@ -129,10 +129,10 @@ std::vector extract_perimeter_extrusions( const bool is_hole = loop->is_clockwise(); reverse_loop = print.config().prefer_clockwise_movements ? !is_hole : is_hole; } - SmoothPath path{smooth_path(&layer, ExtrusionEntityReference{*ee, reverse_loop}, extruder_id, last_position)}; + auto [path, wipe_offset]{smooth_path(&layer, ®ion, ExtrusionEntityReference{*ee, reverse_loop}, extruder_id, last_position)}; previous_position = get_gcode_point(last_position, offset); if (!path.empty()) { - result.push_back(Perimeter{std::move(path), reverse_loop, ee}); + result.push_back(Perimeter{std::move(path), reverse_loop, ee, wipe_offset}); } } } @@ -194,7 +194,7 @@ std::vector extract_infill_ranges( std::vector paths; for (const ExtrusionEntityReference &extrusion_reference : sorted_extrusions) { std::optional last_position{get_instance_point(previous_position, offset)}; - SmoothPath path{smooth_path(&layer, extrusion_reference, extruder_id, last_position)}; + auto [path, _]{smooth_path(&layer, ®ion, extrusion_reference, extruder_id, last_position)}; if (!path.empty()) { paths.push_back(std::move(path)); } @@ -367,7 +367,7 @@ std::vector get_support_extrusions( if (collection != nullptr) { for (const ExtrusionEntity * sub_entity : *collection) { std::optional last_position{get_instance_point(previous_position, {0, 0})}; - SmoothPath path{smooth_path(nullptr, {*sub_entity, entity_reference.flipped()}, extruder_id, last_position)}; + auto [path, _]{smooth_path(nullptr, nullptr, {*sub_entity, entity_reference.flipped()}, extruder_id, last_position)}; if (!path.empty()) { paths.push_back({std::move(path), is_interface}); } @@ -375,7 +375,7 @@ std::vector get_support_extrusions( } } else { std::optional last_position{get_instance_point(previous_position, {0, 0})}; - SmoothPath path{smooth_path(nullptr, entity_reference, extruder_id, last_position)}; + auto [path, _]{smooth_path(nullptr, nullptr, entity_reference, extruder_id, last_position)}; if (!path.empty()) { paths.push_back({std::move(path), is_interface}); } @@ -561,7 +561,7 @@ std::vector get_extrusions( } const ExtrusionEntityReference entity{*print.skirt().entities[i], reverse}; std::optional last_position{get_instance_point(previous_position, {0, 0})}; - SmoothPath path{smooth_path(nullptr, entity, extruder_id, last_position)}; + auto [path, _]{smooth_path(nullptr, nullptr, entity, extruder_id, last_position)}; previous_position = get_gcode_point(last_position, {0, 0}); extruder_extrusions.skirt.emplace_back(i, std::move(path)); } @@ -580,7 +580,7 @@ std::vector get_extrusions( const ExtrusionEntityReference entity_reference{*entity, reverse}; std::optional last_position{get_instance_point(previous_position, {0, 0})}; - SmoothPath path{smooth_path(nullptr, entity_reference, extruder_id, last_position)}; + auto [path, _]{smooth_path(nullptr, nullptr, entity_reference, extruder_id, last_position)}; previous_position = get_gcode_point(last_position, {0, 0}); extruder_extrusions.brim.push_back({std::move(path), is_loop}); } @@ -607,16 +607,16 @@ std::vector get_extrusions( return extrusions; } -std::optional get_first_point(const SmoothPath &path) { +std::optional get_first_point(const SmoothPath &path) { for (const SmoothPathElement & element : path) { if (!element.path.empty()) { - return InstancePoint{element.path.front().point}; + return element.path.front(); } } return std::nullopt; } -std::optional get_first_point(const std::vector &smooth_paths) { +std::optional get_first_point(const std::vector &smooth_paths) { for (const SmoothPath &path : smooth_paths) { if (auto result = get_first_point(path)) { return result; @@ -625,7 +625,7 @@ std::optional get_first_point(const std::vector &smoo return std::nullopt; } -std::optional get_first_point(const std::vector &infill_ranges) { +std::optional get_first_point(const std::vector &infill_ranges) { for (const InfillRange &infill_range : infill_ranges) { if (auto result = get_first_point(infill_range.items)) { return result; @@ -634,7 +634,7 @@ std::optional get_first_point(const std::vector &inf return std::nullopt; } -std::optional get_first_point(const std::vector &perimeters) { +std::optional get_first_point(const std::vector &perimeters) { for (const Perimeter &perimeter : perimeters) { if (auto result = get_first_point(perimeter.smooth_path)) { return result; @@ -643,7 +643,7 @@ std::optional get_first_point(const std::vector &perim return std::nullopt; } -std::optional get_first_point(const std::vector &extrusions) { +std::optional get_first_point(const std::vector &extrusions) { for (const IslandExtrusions &island : extrusions) { if (island.infill_first) { if (auto result = get_first_point(island.infill_ranges)) { @@ -664,7 +664,7 @@ std::optional get_first_point(const std::vector return std::nullopt; } -std::optional get_first_point(const std::vector &extrusions) { +std::optional get_first_point(const std::vector &extrusions) { for (const SliceExtrusions &slice : extrusions) { if (auto result = get_first_point(slice.common_extrusions)) { return result; @@ -673,44 +673,47 @@ std::optional get_first_point(const std::vector return std::nullopt; } -std::optional get_first_point(const ExtruderExtrusions &extrusions) { +std::optional get_first_point(const ExtruderExtrusions &extrusions) { for (const auto&[_, path] : extrusions.skirt) { if (auto result = get_first_point(path)) { - return result->local_point; + return result; }; } for (const BrimPath &brim_path : extrusions.brim) { if (auto result = get_first_point(brim_path.path)) { - return result->local_point; + return result; }; } for (const OverridenExtrusions &overriden_extrusions : extrusions.overriden_extrusions) { if (auto result = get_first_point(overriden_extrusions.slices_extrusions)) { - return result->local_point - overriden_extrusions.instance_offset; + result->point += overriden_extrusions.instance_offset; + return result; } } for (const NormalExtrusions &normal_extrusions : extrusions.normal_extrusions) { for (const SupportPath &support_path : normal_extrusions.support_extrusions) { if (auto result = get_first_point(support_path.path)) { - return result->local_point + normal_extrusions.instance_offset; + result->point += normal_extrusions.instance_offset; + return result; } } if (auto result = get_first_point(normal_extrusions.slices_extrusions)) { - return result->local_point + normal_extrusions.instance_offset; + result->point += normal_extrusions.instance_offset; + return result; } } return std::nullopt; } -std::optional get_first_point(const std::vector &extrusions) { +std::optional get_first_point(const std::vector &extrusions) { if (extrusions.empty()) { return std::nullopt; } if (extrusions.front().wipe_tower_start) { - return extrusions.front().wipe_tower_start; + return {{*(extrusions.front().wipe_tower_start)}}; } for (const ExtruderExtrusions &extruder_extrusions : extrusions) { diff --git a/src/libslic3r/GCode/ExtrusionOrder.hpp b/src/libslic3r/GCode/ExtrusionOrder.hpp index c9409f07ae..dcd2abefc0 100644 --- a/src/libslic3r/GCode/ExtrusionOrder.hpp +++ b/src/libslic3r/GCode/ExtrusionOrder.hpp @@ -83,6 +83,7 @@ struct Perimeter { GCode::SmoothPath smooth_path; bool reversed; const ExtrusionEntity *extrusion_entity; + std::size_t wipe_offset; }; struct IslandExtrusions { @@ -122,8 +123,9 @@ struct InstancePoint { Point local_point; }; -using PathSmoothingFunction = std::function &previous_position +using PathSmoothingResult = std::pair; +using PathSmoothingFunction = std::function &previous_position )>; struct BrimPath { @@ -158,7 +160,7 @@ std::vector get_extrusions( std::optional previous_position ); -std::optional get_first_point(const std::vector &extrusions); +std::optional get_first_point(const std::vector &extrusions); const PrintInstance * get_first_instance( const std::vector &extrusions, diff --git a/src/libslic3r/GCode/GCodeWriter.cpp b/src/libslic3r/GCode/GCodeWriter.cpp index eda530cb3c..aa211c8634 100644 --- a/src/libslic3r/GCode/GCodeWriter.cpp +++ b/src/libslic3r/GCode/GCodeWriter.cpp @@ -387,7 +387,7 @@ std::string GCodeWriter::get_travel_to_z_gcode(double z, const std::string_view std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std::string_view comment) { - assert(dE != 0); + //assert(dE != 0); assert(std::abs(dE) < 1000.0); m_pos.head<2>() = point.head<2>(); @@ -399,6 +399,17 @@ std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std: return w.string(); } +std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std::string_view comment) +{ + m_pos = point; + + GCodeG1Formatter w; + w.emit_xyz(point); + w.emit_e(m_extrusion_axis, m_extruder->extrude(dE).second); + w.emit_comment(this->config.gcode_comments, comment); + return w.string(); +} + std::string GCodeWriter::extrude_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, double dE, const std::string_view comment) { assert(std::abs(dE) < 1000.0); diff --git a/src/libslic3r/GCode/GCodeWriter.hpp b/src/libslic3r/GCode/GCodeWriter.hpp index 4d598f5796..26b1bc383e 100644 --- a/src/libslic3r/GCode/GCodeWriter.hpp +++ b/src/libslic3r/GCode/GCodeWriter.hpp @@ -92,6 +92,7 @@ public: std::string get_travel_to_z_gcode(double z, const std::string_view comment) const; std::string travel_to_z(double z, const std::string_view comment = {}); std::string extrude_to_xy(const Vec2d &point, double dE, const std::string_view comment = {}); + std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string_view comment = {}); std::string extrude_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, double dE, const std::string_view comment); // std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string_view comment = {}); std::string retract(bool before_wipe = false); @@ -211,6 +212,10 @@ public: } void emit_e(const std::string_view axis, double v) { + const double precision{std::pow(10.0, -E_EXPORT_DIGITS)}; + if (std::abs(v) < precision) { + v = v < 0 ? -precision : precision; + } if (! axis.empty()) { // not gcfNoExtrusion this->emit_axis(axis[0], v, E_EXPORT_DIGITS); diff --git a/src/libslic3r/GCode/SeamAligned.cpp b/src/libslic3r/GCode/SeamAligned.cpp index 7a16d0bfea..62ba10dc03 100644 --- a/src/libslic3r/GCode/SeamAligned.cpp +++ b/src/libslic3r/GCode/SeamAligned.cpp @@ -122,8 +122,8 @@ std::optional snap_to_angle( } return false; }}; - Geometry::visit_near_backward(search_start, positions.size(), visitor); - Geometry::visit_near_forward(search_start, positions.size(), visitor); + Geometry::visit_backward(search_start, positions.size(), visitor); + Geometry::visit_forward(search_start, positions.size(), visitor); if (match) { return match; } @@ -131,8 +131,8 @@ std::optional snap_to_angle( min_distance = std::numeric_limits::infinity(); angle_type = AngleType::concave; - Geometry::visit_near_backward(search_start, positions.size(), visitor); - Geometry::visit_near_forward(search_start, positions.size(), visitor); + Geometry::visit_backward(search_start, positions.size(), visitor); + Geometry::visit_forward(search_start, positions.size(), visitor); return match; } diff --git a/src/libslic3r/GCode/SeamGeometry.cpp b/src/libslic3r/GCode/SeamGeometry.cpp index fbc4d07d93..d87fb49130 100644 --- a/src/libslic3r/GCode/SeamGeometry.cpp +++ b/src/libslic3r/GCode/SeamGeometry.cpp @@ -66,7 +66,7 @@ Vec2d get_polygon_normal( std::optional previous_index; std::optional next_index; - visit_near_forward(index, points.size(), [&](const std::size_t index_candidate) { + visit_forward(index, points.size(), [&](const std::size_t index_candidate) { if (index == index_candidate) { return false; } @@ -77,7 +77,7 @@ Vec2d get_polygon_normal( } return false; }); - visit_near_backward(index, points.size(), [&](const std::size_t index_candidate) { + visit_backward(index, points.size(), [&](const std::size_t index_candidate) { const double distance{(points[index_candidate] - points[index]).norm()}; if (distance > min_arm_length) { previous_index = index_candidate; @@ -280,14 +280,14 @@ std::vector oversample_edge(const Vec2d &from, const Vec2d &to, const dou return result; } -void visit_near_forward( +void visit_forward( const std::size_t start_index, const std::size_t loop_size, const std::function &visitor ) { std::size_t last_index{loop_size - 1}; std::size_t index{start_index}; - for (unsigned _{0}; _ < 30; ++_) { // Do not visit too far. + for (unsigned _{0}; _ < loop_size + 1; ++_) { if (visitor(index)) { return; } @@ -295,14 +295,14 @@ void visit_near_forward( } } -void visit_near_backward( +void visit_backward( const std::size_t start_index, const std::size_t loop_size, const std::function &visitor ) { std::size_t last_index{loop_size - 1}; std::size_t index{start_index == 0 ? loop_size - 1 : start_index - 1}; - for (unsigned _{0}; _ < 30; ++_) { // Do not visit too far. + for (unsigned _{0}; _ < loop_size; ++_) { if (visitor(index)) { return; } @@ -374,7 +374,7 @@ std::vector get_vertex_angles(const std::vector &points, const do std::optional previous_index; std::optional next_index; - visit_near_forward(index, points.size(), [&](const std::size_t index_candidate) { + visit_forward(index, points.size(), [&](const std::size_t index_candidate) { if (index == index_candidate) { return false; } @@ -385,7 +385,7 @@ std::vector get_vertex_angles(const std::vector &points, const do } return false; }); - visit_near_backward(index, points.size(), [&](const std::size_t index_candidate) { + visit_backward(index, points.size(), [&](const std::size_t index_candidate) { const double distance{(points[index_candidate] - points[index]).norm()}; if (distance > min_arm_length) { previous_index = index_candidate; @@ -440,4 +440,57 @@ Polygon to_polygon(const ExtrusionLoop &loop) { } return Polygon{loop_points}; } + +std::optional offset_along_loop_lines( + const Vec2d &point, + const std::size_t loop_line_index, + const Linesf &loop_lines, + const double offset, + const Direction1D direction +) { + const Linef initial_line{loop_lines[loop_line_index]}; + double distance{ + direction == Direction1D::forward ? (initial_line.b - point).norm() : + (point - initial_line.a).norm()}; + if (distance >= offset) { + const Vec2d edge_direction{(initial_line.b - initial_line.a).normalized()}; + const Vec2d offset_point{direction == Direction1D::forward ? Vec2d{point + offset * edge_direction} : Vec2d{point - offset * edge_direction}}; + return {{offset_point, loop_line_index}}; + } + + std::optional offset_point; + + bool skip_first{direction == Direction1D::forward}; + const auto visitor{[&](std::size_t index) { + if (skip_first) { + skip_first = false; + return false; + } + const Vec2d previous_point{ + direction == Direction1D::forward ? loop_lines[index].a : loop_lines[index].b}; + const Vec2d next_point{ + direction == Direction1D::forward ? loop_lines[index].b : loop_lines[index].a}; + const Vec2d edge{next_point - previous_point}; + + if (distance + edge.norm() > offset) { + const double remaining_distance{offset - distance}; + offset_point = + PointOnLine{previous_point + remaining_distance * edge.normalized(), index}; + return true; + } + + distance += edge.norm(); + + return false; + }}; + + if (direction == Direction1D::forward) { + Geometry::visit_forward(loop_line_index, loop_lines.size(), visitor); + } else { + Geometry::visit_backward(loop_line_index, loop_lines.size(), visitor); + } + + return offset_point; +} + } // namespace Slic3r::Seams::Geometry diff --git a/src/libslic3r/GCode/SeamGeometry.hpp b/src/libslic3r/GCode/SeamGeometry.hpp index c861e3d40d..2eff2dae89 100644 --- a/src/libslic3r/GCode/SeamGeometry.hpp +++ b/src/libslic3r/GCode/SeamGeometry.hpp @@ -147,12 +147,12 @@ void iterate_nested(const NestedVector &nested_vector, const std::function &visitor ); -void visit_near_backward( +void visit_backward( const std::size_t start_index, const std::size_t loop_size, const std::function &visitor @@ -192,6 +192,24 @@ std::pair pick_closest_bounding_box( Polygon to_polygon(const ExtrusionLoop &loop); +enum class Direction1D { + forward, + backward +}; + +struct PointOnLine{ + Vec2d point; + std::size_t line_index; +}; + +std::optional offset_along_loop_lines( + const Vec2d &point, + const std::size_t loop_line_index, + const Linesf &loop_lines, + const double offset, + const Direction1D direction +); + } // namespace Slic3r::Seams::Geometry #endif // libslic3r_SeamGeometry_hpp_ diff --git a/src/libslic3r/GCode/SeamPerimeters.cpp b/src/libslic3r/GCode/SeamPerimeters.cpp index d8879f93b7..214073878c 100644 --- a/src/libslic3r/GCode/SeamPerimeters.cpp +++ b/src/libslic3r/GCode/SeamPerimeters.cpp @@ -164,7 +164,7 @@ std::vector merge_angle_types( resulting_type = smooth_angle_type; // Check if there is a sharp angle in the vicinity. If so, do not use the smooth angle. - Geometry::visit_near_forward(index, angle_types.size(), [&](const std::size_t forward_index) { + Geometry::visit_forward(index, angle_types.size(), [&](const std::size_t forward_index) { const double distance{(points[forward_index] - points[index]).norm()}; if (distance > min_arm_length) { return true; @@ -174,7 +174,7 @@ std::vector merge_angle_types( } return false; }); - Geometry::visit_near_backward(index, angle_types.size(), [&](const std::size_t backward_index) { + Geometry::visit_backward(index, angle_types.size(), [&](const std::size_t backward_index) { const double distance{(points[backward_index] - points[index]).norm()}; if (distance > min_arm_length) { return true; diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index 8517732624..5b232b56bf 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -10,6 +10,7 @@ #include "SeamPlacer.hpp" +#include "libslic3r/ClipperUtils.hpp" #include "libslic3r/GCode/SeamShells.hpp" #include "libslic3r/GCode/SeamAligned.hpp" #include "libslic3r/GCode/SeamRear.hpp" @@ -216,68 +217,116 @@ std::pair project_to_extrusion_loop( return {loop_line_index, loop_point}; } -std::optional offset_along_loop_lines( - const Vec2d &point, - const std::size_t loop_line_index, - const Linesf &loop_lines, - const double offset -) { - double distance{0}; - Vec2d previous_point{point}; - std::optional offset_point; - Geometry::visit_near_forward(loop_line_index, loop_lines.size(), [&](std::size_t index) { - const Vec2d next_point{loop_lines[index].b}; - const Vec2d edge{next_point - previous_point}; - - if (distance + edge.norm() > offset) { - const double remaining_distance{offset - distance}; - offset_point = previous_point + remaining_distance * edge.normalized(); - return true; - } - - distance += edge.norm(); - previous_point = next_point; - - return false; - }); - - return offset_point; -} - double get_angle(const SeamChoice &seam_choice, const Perimeters::Perimeter &perimeter) { const bool is_at_vertex{seam_choice.previous_index == seam_choice.next_index}; return is_at_vertex ? perimeter.angles[seam_choice.previous_index] : 0.0; } -Point finalize_seam_position( - const Polygon &loop_polygon, - const SeamChoice &seam_choice, - const Perimeters::Perimeter &perimeter, - const double loop_width, - const bool do_staggering +SeamChoice to_seam_choice( + const Geometry::PointOnLine &point_on_line, const Perimeters::Perimeter &perimeter ) { + SeamChoice result; + + result.position = point_on_line.point; + result.previous_index = point_on_line.line_index; + result.next_index = point_on_line.line_index == perimeter.positions.size() - 1 ? + 0 : + point_on_line.line_index + 1; + return result; +} + +std::variant finalize_seam_position( + const ExtrusionLoop &loop, + const PrintRegion *region, + SeamChoice seam_choice, + const Perimeters::Perimeter &perimeter, + const bool staggered_inner_seams, + const bool flipped +) { + const Polygon loop_polygon{Geometry::to_polygon(loop)}; + const bool do_staggering{staggered_inner_seams && loop.role() == ExtrusionRole::Perimeter}; + const double loop_width{loop.paths.empty() ? 0.0 : loop.paths.front().width()}; + + const ExPolygon perimeter_polygon{Geometry::scaled(perimeter.positions)}; + const Linesf perimeter_lines{to_unscaled_linesf({perimeter_polygon})}; const Linesf loop_lines{to_unscaled_linesf({ExPolygon{loop_polygon}})}; - const auto [loop_line_index, loop_point]{ + + auto [loop_line_index, loop_point]{ project_to_extrusion_loop(seam_choice, perimeter, loop_lines)}; + const Geometry::Direction1D offset_direction{ + flipped ? Geometry::Direction1D::forward : Geometry::Direction1D::backward}; + // ExtrusionRole::Perimeter is inner perimeter. if (do_staggering) { const double depth = (loop_point - seam_choice.position).norm() - loop_width / 2.0; - const double angle{get_angle(seam_choice, perimeter)}; - const double initial_offset{angle > 0 ? angle / 2.0 * depth : 0.0}; - const double additional_offset{angle < 0 ? std::cos(angle / 2.0) * depth : depth}; - const double staggering_offset{initial_offset + additional_offset}; + const double staggering_offset{depth}; - std::optional staggered_point{ - offset_along_loop_lines(loop_point, loop_line_index, loop_lines, staggering_offset)}; + std::optional staggered_point{Geometry::offset_along_loop_lines( + loop_point, seam_choice.previous_index, perimeter_lines, staggering_offset, + offset_direction + )}; if (staggered_point) { - return scaled(*staggered_point); + seam_choice = to_seam_choice(*staggered_point, perimeter); + std::tie(loop_line_index, loop_point) = project_to_extrusion_loop(seam_choice, perimeter, loop_lines); } } + bool place_scarf_seam { + region->config().scarf_seam_placement == ScarfSeamPlacement::everywhere + || (region->config().scarf_seam_placement == ScarfSeamPlacement::countours && !perimeter.is_hole) + }; + const bool is_smooth{ + seam_choice.previous_index != seam_choice.next_index || + perimeter.angle_types[seam_choice.previous_index] == Perimeters::AngleType::smooth + }; + + if (region->config().scarf_seam_only_on_smooth && !is_smooth) { + place_scarf_seam = false; + } + + if (place_scarf_seam) { + Scarf::Scarf scarf{}; + scarf.length = region->config().scarf_seam_length; + scarf.entire_loop = region->config().scarf_seam_entire_loop; + scarf.max_segment_length = region->config().scarf_seam_max_segment_length; + scarf.start_height = region->config().scarf_seam_start_height.get_abs_value(1.0); + + if (loop.role() == ExtrusionRole::Perimeter) { // Inner perimeter + const double offset{scarf.entire_loop ? 0 : scarf.length}; + + const ExPolygons shrank_polygons{shrink_ex({perimeter_polygon}, scaled(loop_width / 2.0))}; + if (shrank_polygons.empty()) { + return scaled(loop_point); + } + const std::optional outter_scarf_start_point{Geometry::offset_along_loop_lines( + seam_choice.position, seam_choice.previous_index, to_unscaled_linesf({shrank_polygons.front()}), offset, offset_direction + )}; + if (!outter_scarf_start_point) { + return scaled(loop_point); + } + + const auto [end_point_previous_index, end_point]{project_to_extrusion_loop( + to_seam_choice(*outter_scarf_start_point, perimeter), perimeter, loop_lines + )}; + + if (!region->config().scarf_seam_on_inner_perimeters) { + return scaled(end_point); + } + + scarf.end_point = scaled(end_point); + scarf.end_point_previous_index = end_point_previous_index; + } else { // Outter perimeter + scarf.end_point = scaled(loop_point); + scarf.end_point_previous_index = loop_line_index; + } + + return scarf; + } + return scaled(loop_point); } @@ -354,7 +403,9 @@ int get_perimeter_count(const Layer *layer){ return count; } -Point Placer::place_seam(const Layer *layer, const ExtrusionLoop &loop, const Point &last_pos) const { +std::variant Placer::place_seam( + const Layer *layer, const PrintRegion *region, const ExtrusionLoop &loop, const bool flipped, const Point &last_pos +) const { const PrintObject *po = layer->object(); // Must not be called with supprot layer. assert(dynamic_cast(layer) == nullptr); @@ -362,16 +413,15 @@ Point Placer::place_seam(const Layer *layer, const ExtrusionLoop &loop, const Po assert(layer->id() >= po->slicing_parameters().raft_layers()); const size_t layer_index = layer->id() - po->slicing_parameters().raft_layers(); - const Polygon loop_polygon{Geometry::to_polygon(loop)}; - - const bool do_staggering{this->params.staggered_inner_seams && loop.role() == ExtrusionRole::Perimeter}; - const double loop_width{loop.paths.empty() ? 0.0 : loop.paths.front().width()}; - - if (po->config().seam_position.value == spNearest) { - const std::vector &perimeters{this->perimeters_per_layer.at(po)[layer_index]}; - const auto [seam_choice, perimeter_index] = place_seam_near(perimeters, loop, last_pos, this->params.max_nearest_detour); - return finalize_seam_position(loop_polygon, seam_choice, perimeters[perimeter_index].perimeter, loop_width, do_staggering); + const std::vector &perimeters{ + this->perimeters_per_layer.at(po)[layer_index]}; + const auto [seam_choice, perimeter_index] = + place_seam_near(perimeters, loop, last_pos, this->params.max_nearest_detour); + return finalize_seam_position( + loop, region, seam_choice, perimeters[perimeter_index].perimeter, + this->params.staggered_inner_seams, flipped + ); } else { const std::vector &seams_on_perimeters{this->seams_per_object.at(po)[layer_index]}; @@ -387,14 +437,18 @@ Point Placer::place_seam(const Layer *layer, const ExtrusionLoop &loop, const Po seams_on_perimeters[0].perimeter.is_hole ? seams_on_perimeters[1] : seams_on_perimeters[0]}; return finalize_seam_position( - loop_polygon, seam_perimeter_choice.choice, seam_perimeter_choice.perimeter, - loop_width, do_staggering + loop, region, seam_perimeter_choice.choice, seam_perimeter_choice.perimeter, + this->params.staggered_inner_seams, flipped ); } } - const SeamPerimeterChoice &seam_perimeter_choice{choose_closest_seam(seams_on_perimeters, loop_polygon)}; - return finalize_seam_position(loop_polygon, seam_perimeter_choice.choice, seam_perimeter_choice.perimeter, loop_width, do_staggering); + const SeamPerimeterChoice &seam_perimeter_choice{ + choose_closest_seam(seams_on_perimeters, Geometry::to_polygon(loop))}; + return finalize_seam_position( + loop, region, seam_perimeter_choice.choice, seam_perimeter_choice.perimeter, + this->params.staggered_inner_seams, flipped + ); } } } // namespace Slic3r::Seams diff --git a/src/libslic3r/GCode/SeamPlacer.hpp b/src/libslic3r/GCode/SeamPlacer.hpp index 5631aa16a7..77955a7d09 100644 --- a/src/libslic3r/GCode/SeamPlacer.hpp +++ b/src/libslic3r/GCode/SeamPlacer.hpp @@ -12,6 +12,7 @@ #include #include "libslic3r/GCode/SeamAligned.hpp" +#include "libslic3r/GCode/SeamScarf.hpp" #include "libslic3r/Polygon.hpp" #include "libslic3r/Print.hpp" #include "libslic3r/Point.hpp" @@ -37,7 +38,7 @@ struct Params double concave_visibility_modifier{}; Perimeters::PerimeterParams perimeter; Slic3r::ModelInfo::Visibility::Params visibility; - bool staggered_inner_seams; + bool staggered_inner_seams{}; }; std::ostream& operator<<(std::ostream& os, const Params& params); @@ -53,7 +54,9 @@ public: const std::function &throw_if_canceled ); - Point place_seam(const Layer *layer, const ExtrusionLoop &loop, const Point &last_pos) const; + std::variant place_seam( + const Layer *layer, const PrintRegion *region, const ExtrusionLoop &loop, const bool flipped, const Point &last_pos + ) const; private: Params params; diff --git a/src/libslic3r/GCode/SeamScarf.cpp b/src/libslic3r/GCode/SeamScarf.cpp new file mode 100644 index 0000000000..babbd369b8 --- /dev/null +++ b/src/libslic3r/GCode/SeamScarf.cpp @@ -0,0 +1,334 @@ +#include "libslic3r/GCode/SeamScarf.hpp" +#include "libslic3r/GCode/SmoothPath.hpp" + +namespace Slic3r::Seams::Scarf { + +namespace Impl { +PathPoint get_path_point( + const ExtrusionPaths &paths, const Point &point, const std::size_t global_index +) { + std::size_t path_start_index{0}; + for (std::size_t path_index{0}; path_index < paths.size(); ++path_index) { + const ExtrusionPath &path{paths[path_index]}; + if (global_index - path_start_index < path.size()) { + return {point, path_index, global_index - path_start_index}; + } + path_start_index += path.size(); + } + throw std::runtime_error("Failed translating global path index!"); +} + +std::pair split_path( + const ExtrusionPath &path, const Point &point, const std::size_t point_previous_index +) { + if (static_cast(point_previous_index) >= static_cast(path.size()) - 1) { + throw std::runtime_error( + "Invalid path split index " + std::to_string(point_previous_index) + + " for path of size " + std::to_string(path.size()) + "!" + ); + } + const Point previous_point{path.polyline.points.at(point_previous_index)}; + const Point next_point{path.polyline.points.at(point_previous_index + 1)}; + + Polyline first; + for (std::size_t i{0}; i <= point_previous_index; ++i) { + first.points.push_back(path.polyline[i]); + } + first.points.push_back(point); + Polyline second; + second.points.push_back(point); + + for (std::size_t i{point_previous_index + 1}; i < path.size(); ++i) { + second.points.push_back(path.polyline[i]); + } + + return {ExtrusionPath{first, path.attributes()}, ExtrusionPath{second, path.attributes()}}; +} + +ExtrusionPaths split_paths(ExtrusionPaths &&paths, const PathPoint &path_point) { + ExtrusionPaths result{std::move(paths)}; + std::pair split{ + split_path(result[path_point.path_index], path_point.point, path_point.previous_point_on_path_index)}; + + const auto path_iterator{result.begin() + path_point.path_index}; + result.erase(path_iterator); + result.insert(path_iterator, split.second); + result.insert(path_iterator, split.first); + + return result; +} + +double get_length(tcb::span smooth_path) { + if (smooth_path.empty() || smooth_path.front().path.empty()) { + return 0; + } + + double result{0}; + + Point previous_point{smooth_path.front().path.front().point}; + + for (const GCode::SmoothPathElement &element : smooth_path) { + for (const Geometry::ArcWelder::Segment &segment : element.path) { + result += (segment.point - previous_point).cast().norm(); + previous_point = segment.point; + } + } + return result; +} + +GCode::SmoothPath convert_to_smooth(tcb::span paths) { + GCode::SmoothPath result; + for (const ExtrusionPath &path : paths) { + Geometry::ArcWelder::Path smooth_path; + for (const Point &point : path.polyline) { + smooth_path.push_back(Geometry::ArcWelder::Segment{point}); + } + result.push_back({path.attributes(), smooth_path}); + } + + return result; +} + +Points linspace(const Point &from, const Point &to, const std::size_t count) { + if (count < 2) { + throw std::runtime_error("Invalid count for linspace!"); + } + + Points result; + result.push_back(from); + + Point offset{(to - from) / (count - 1)}; + for (std::size_t i{1}; i < count - 1; ++i) { + result.push_back(from + i * offset); + } + result.push_back(to); + + return result; +} + +Points ensure_max_distance(const Points &points, const double max_distance) { + if (points.size() < 2) { + return points; + } + + Points result; + result.push_back(points.front()); + for (std::size_t i{1}; i < points.size(); ++i) { + const Point ¤t_point{points[i]}; + const Point &previous_point{points[i - 1]}; + const double distance = (current_point - previous_point).cast().norm(); + + if (distance > max_distance) { + const std::size_t points_count{ + static_cast(std::ceil(distance / max_distance)) + 1}; + const Points subdivided{linspace(previous_point, current_point, points_count)}; + result.insert(result.end(), std::next(subdivided.begin()), subdivided.end()); + } else { + result.push_back(current_point); + } + } + return result; +} + +ExtrusionPaths ensure_scarf_resolution( + ExtrusionPaths &&paths, const std::size_t scarf_paths_count, const double max_distance +) { + ExtrusionPaths result{std::move(paths)}; + auto scarf{tcb::span{result}.first(scarf_paths_count)}; + + for (ExtrusionPath &path : scarf) { + path.polyline.points = ensure_max_distance(path.polyline.points, max_distance); + } + + return result; +} + +GCode::SmoothPath lineary_increase_extrusion_height( + GCode::SmoothPath &&smooth_path, const double start_height +) { + GCode::SmoothPath result{std::move(smooth_path)}; + const double length{get_length(result)}; + double distance{0}; + + std::optional previous_point{}; + for (GCode::SmoothPathElement &element : result) { + for (Geometry::ArcWelder::Segment &segment : element.path) { + if (!previous_point) { + segment.e_fraction = 0; + segment.height_fraction = start_height; + } else { + distance += (segment.point - *previous_point).cast().norm(); + + if (distance >= length) { + segment.e_fraction = 1.0; + segment.height_fraction = 1.0; + } else { + segment.e_fraction = distance / length; + segment.height_fraction = start_height + (distance / length) * (1.0 - start_height); + } + } + previous_point = segment.point; + } + } + + return result; +} + +GCode::SmoothPath lineary_readuce_extrusion_amount( + GCode::SmoothPath &&smooth_path +) { + GCode::SmoothPath result{std::move(smooth_path)}; + const double length{get_length(result)}; + double distance{0}; + + std::optional previous_point{}; + for (GCode::SmoothPathElement &element : result) { + for (Geometry::ArcWelder::Segment &segment : element.path) { + if (!previous_point) { + segment.e_fraction = 1.0; + } else { + distance += (segment.point - *previous_point).cast().norm(); + + if (distance >= length) { + segment.e_fraction = 0.0; + } else { + segment.e_fraction = 1.0 - distance / length; + } + } + previous_point = segment.point; + } + } + + return result; +} + +GCode::SmoothPath elevate_scarf( + const ExtrusionPaths &paths, + const std::size_t scarf_paths_count, + const std::function)> &apply_smoothing, + const double start_height +) { + const auto scarf_at_start{tcb::span{paths}.first(scarf_paths_count)}; + GCode::SmoothPath first_segment{convert_to_smooth(scarf_at_start)}; + first_segment = + lineary_increase_extrusion_height(std::move(first_segment), start_height); + + std::size_t normal_extrusions_size{paths.size() - 2 * scarf_paths_count}; + const auto normal_extrusions{ + tcb::span{paths}.subspan(scarf_paths_count, normal_extrusions_size)}; + const GCode::SmoothPath middle_segment{apply_smoothing(normal_extrusions)}; + + const auto scarf_at_end{tcb::span{paths}.last(scarf_paths_count)}; + GCode::SmoothPath last_segment{convert_to_smooth(scarf_at_end)}; + last_segment = + lineary_readuce_extrusion_amount(std::move(last_segment)); + + first_segment.insert(first_segment.end(), middle_segment.begin(), middle_segment.end()); + first_segment.insert(first_segment.end(), last_segment.begin(), last_segment.end()); + + return first_segment; +} + +std::optional get_point_offset_from_end(const ExtrusionPaths &paths, const double length) { + double distance{0.0}; + + if (paths.empty()) { + return std::nullopt; + } + for (int path_index{static_cast(paths.size() - 1)}; path_index >= 0; --path_index) { + const ExtrusionPath &path{paths[path_index]}; + if (path.polyline.size() < 2) { + throw std::runtime_error( + "Invalid path: less than two points: " + std::to_string(path.size()) + "!" + ); + } + for (int point_index{static_cast(path.polyline.size() - 2)}; point_index >= 0; + --point_index) { + const Point &previous_point{path.polyline[point_index + 1]}; + const Point ¤t_point{path.polyline[point_index]}; + const Vec2d edge{(current_point - previous_point).cast()}; + const double edge_length{edge.norm()}; + const Vec2d edge_direction{edge.normalized()}; + if (distance + edge_length > length) { + return PathPoint{ + previous_point + (edge_direction * (length - distance)).cast(), + static_cast(path_index), static_cast(point_index)}; + } + distance += edge_length; + } + } + return std::nullopt; +} + +ExtrusionPaths reverse(ExtrusionPaths &&paths) { + ExtrusionPaths result{std::move(paths)}; + std::reverse(result.begin(), result.end()); + for (ExtrusionPath &path : result) { + std::reverse(path.polyline.begin(), path.polyline.end()); + } + return result; +} +} // namespace Impl + +std::pair add_scarf_seam( + ExtrusionPaths &&paths, + const Scarf &scarf, + const std::function)> &apply_smoothing, + const bool flipped +) { + Impl::PathPoint end_point{ + Impl::get_path_point(paths, scarf.end_point, scarf.end_point_previous_index)}; + + const ExtrusionPath &path{paths[end_point.path_index]}; + if (end_point.previous_point_on_path_index == static_cast(path.size()) - 1) { + // Last point of the path is picked. This is invalid for splitting. + if (static_cast(end_point.path_index) < static_cast(paths.size()) - 2) { + // First point of next path and last point of previous path should be the same. + // Pick the first point of the next path. + end_point = {end_point.point, end_point.path_index + 1, 0}; + } else { + // There is no next path. + // This should be very rare case. + if (end_point.previous_point_on_path_index == 0) { + throw std::runtime_error("Could not split path!"); + } + end_point = {end_point.point, end_point.path_index, end_point.previous_point_on_path_index - 1}; + } + } + + paths = split_paths(std::move(paths), end_point); + + // End with scarf. + std::rotate(paths.begin(), std::next(paths.begin(), end_point.path_index + 1), paths.end()); + + if (flipped) { + paths = Impl::reverse(std::move(paths)); + } + + std::optional start_point; + if (!scarf.entire_loop) { + start_point = Impl::get_point_offset_from_end(paths, scaled(scarf.length)); + } + if (!start_point) { + start_point = Impl::PathPoint{ + paths.front().first_point(), + 0, + 0 + }; + } + paths = split_paths(std::move(paths), *start_point); + + const std::size_t scarf_paths_count{paths.size() - start_point->path_index - 1}; + // Start with scarf. + std::rotate(paths.begin(), std::next(paths.begin(), start_point->path_index + 1), paths.end()); + + const double max_distance{scale_(scarf.max_segment_length)}; + paths = Impl::ensure_scarf_resolution(std::move(paths), scarf_paths_count, max_distance); + // This reserve protects agains iterator invalidation. + paths.reserve(paths.size() + scarf_paths_count); + std::copy_n(paths.begin(), scarf_paths_count, std::back_inserter(paths)); + + GCode::SmoothPath smooth_path{Impl::elevate_scarf(paths, scarf_paths_count, apply_smoothing, scarf.start_height)}; + return {std::move(smooth_path), scarf_paths_count}; +} +} // namespace Slic3r::Seams::Scarf diff --git a/src/libslic3r/GCode/SeamScarf.hpp b/src/libslic3r/GCode/SeamScarf.hpp new file mode 100644 index 0000000000..6cf03d6ee8 --- /dev/null +++ b/src/libslic3r/GCode/SeamScarf.hpp @@ -0,0 +1,85 @@ +#ifndef libslic3r_SeamScarf_hpp_ +#define libslic3r_SeamScarf_hpp_ + +#include "libslic3r/ExtrusionEntity.hpp" +#include "tcbspan/span.hpp" + +namespace Slic3r::GCode { + struct SmoothPathElement; + using SmoothPath = std::vector; +} + +namespace Slic3r::Seams::Scarf { + +struct Scarf +{ + Point end_point; + std::size_t end_point_previous_index{}; + double length{}; + double max_segment_length{}; + bool entire_loop{}; + double start_height; +}; + +using SmoothingFunction = std::function)>; + +namespace Impl { +struct PathPoint +{ + Point point; + std::size_t path_index{}; + std::size_t previous_point_on_path_index{}; +}; + +PathPoint get_path_point( + const ExtrusionPaths &paths, const Point &point, const std::size_t global_index +); + +std::pair split_path( + const ExtrusionPath &path, const Point &point, const std::size_t point_previous_index +); + +ExtrusionPaths split_paths(ExtrusionPaths &&paths, const PathPoint &path_point); + +double get_length(tcb::span smooth_path); + +GCode::SmoothPath convert_to_smooth(tcb::span paths); + +/** + * @param count: Points count including the first and last point. + */ +Points linspace(const Point &from, const Point &to, const std::size_t count); + +Points ensure_max_distance(const Points &points, const double max_distance); + +ExtrusionPaths ensure_scarf_resolution( + ExtrusionPaths &&paths, const std::size_t scarf_paths_count, const double max_distance +); + +GCode::SmoothPath lineary_increase_extrusion_height( + GCode::SmoothPath &&smooth_path, const double start_height +); + +GCode::SmoothPath lineary_readuce_extrusion_amount( + GCode::SmoothPath &&smooth_path +); + +GCode::SmoothPath elevate_scarf( + const ExtrusionPaths &paths, + const std::size_t scarf_paths_count, + const SmoothingFunction &apply_smoothing, + const double start_height +); + +std::optional get_point_offset_from_end(const ExtrusionPaths &paths, const double length); +} // namespace Impl + +std::pair add_scarf_seam( + ExtrusionPaths &&paths, + const Scarf &scarf, + const SmoothingFunction &apply_smoothing, + const bool flipped +); +} // namespace Slic3r::Seams::Scarf + +#endif // libslic3r_SeamScarf_hpp_ diff --git a/src/libslic3r/GCode/SmoothPath.cpp b/src/libslic3r/GCode/SmoothPath.cpp index 54ba3c1af2..c08c666222 100644 --- a/src/libslic3r/GCode/SmoothPath.cpp +++ b/src/libslic3r/GCode/SmoothPath.cpp @@ -187,15 +187,15 @@ Geometry::ArcWelder::Path SmoothPathCache::resolve_or_fit(const ExtrusionPath &p return out; } -SmoothPath SmoothPathCache::resolve_or_fit(const ExtrusionPaths &paths, bool reverse, double resolution) const +SmoothPath SmoothPathCache::resolve_or_fit(tcb::span paths, bool reverse, double resolution) const { SmoothPath out; out.reserve(paths.size()); if (reverse) { - for (auto it = paths.crbegin(); it != paths.crend(); ++ it) + for (auto it = paths.rbegin(); it != paths.rend(); ++ it) out.push_back({ it->attributes(), this->resolve_or_fit(*it, true, resolution) }); } else { - for (auto it = paths.cbegin(); it != paths.cend(); ++ it) + for (auto it = paths.begin(); it != paths.end(); ++ it) out.push_back({ it->attributes(), this->resolve_or_fit(*it, false, resolution) }); } return out; @@ -203,14 +203,14 @@ SmoothPath SmoothPathCache::resolve_or_fit(const ExtrusionPaths &paths, bool rev SmoothPath SmoothPathCache::resolve_or_fit(const ExtrusionMultiPath &multipath, bool reverse, double resolution) const { - return this->resolve_or_fit(multipath.paths, reverse, resolution); + return this->resolve_or_fit(tcb::span{multipath.paths}, reverse, resolution); } SmoothPath SmoothPathCache::resolve_or_fit_split_with_seam( const ExtrusionLoop &loop, const bool reverse, const double resolution, const Point &seam_point, const double seam_point_merge_distance_threshold) const { - SmoothPath out = this->resolve_or_fit(loop.paths, reverse, resolution); + SmoothPath out = this->resolve_or_fit(tcb::span{loop.paths}, reverse, resolution); assert(! out.empty()); if (! out.empty()) { // Find a closest point on a vector of smooth paths. diff --git a/src/libslic3r/GCode/SmoothPath.hpp b/src/libslic3r/GCode/SmoothPath.hpp index f4240e8cc1..61ccd91f25 100644 --- a/src/libslic3r/GCode/SmoothPath.hpp +++ b/src/libslic3r/GCode/SmoothPath.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "../ExtrusionEntity.hpp" #include "../Geometry/ArcWelder.hpp" @@ -59,7 +60,7 @@ public: Geometry::ArcWelder::Path resolve_or_fit(const ExtrusionPath &path, bool reverse, double resolution) const; // Look-up a smooth representation of path in the cache. If it does not exist, produce a simplified polyline. - SmoothPath resolve_or_fit(const ExtrusionPaths &paths, bool reverse, double resolution) const; + SmoothPath resolve_or_fit(tcb::span paths, bool reverse, double resolution) const; SmoothPath resolve_or_fit(const ExtrusionMultiPath &path, bool reverse, double resolution) const; // Look-up a smooth representation of path in the cache. If it does not exist, produce a simplified polyline. @@ -78,7 +79,7 @@ public: SmoothPathCaches() = delete; SmoothPathCaches(const SmoothPathCache &global, const SmoothPathCache &layer_local) : m_global(&global), m_layer_local(&layer_local) {} - SmoothPathCaches operator=(const SmoothPathCaches &rhs) + 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; } diff --git a/src/libslic3r/GCode/Travels.cpp b/src/libslic3r/GCode/Travels.cpp index 9da69b99e0..b4a979c5cc 100644 --- a/src/libslic3r/GCode/Travels.cpp +++ b/src/libslic3r/GCode/Travels.cpp @@ -456,7 +456,6 @@ Points3 generate_travel_to_extrusion( ElevatedTravelFormula{elevation_params} )}; - result.emplace_back(xy_path.back().x(), xy_path.back().y(), scaled(initial_elevation)); return result; } } // namespace Slic3r::GCode::Impl::Travels diff --git a/src/libslic3r/GCode/WipeTowerIntegration.cpp b/src/libslic3r/GCode/WipeTowerIntegration.cpp index 0b82e030c7..cdd153d6df 100644 --- a/src/libslic3r/GCode/WipeTowerIntegration.cpp +++ b/src/libslic3r/GCode/WipeTowerIntegration.cpp @@ -63,17 +63,18 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip || will_go_down); // don't dig into the print if (should_travel_to_tower) { const Point xy_point = wipe_tower_point_to_object_point(gcodegen, start_pos); + const Vec3crd to{to_3d(xy_point, scaled(z))}; gcode += gcodegen.m_label_objects.maybe_stop_instance(); gcode += gcodegen.retract_and_wipe(); gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true; const std::string comment{"Travel to a Wipe Tower"}; if (!gcodegen.m_moved_to_first_layer_point) { - const Vec3crd point = to_3d(xy_point, scaled(z)); - gcode += gcodegen.travel_to_first_position(point, current_z, ExtrusionRole::Mixed, [](){return "";}); + gcode += gcodegen.travel_to_first_position(to, current_z, ExtrusionRole::Mixed, [](){return "";}); } else { if (gcodegen.last_position) { + const Vec3crd from{to_3d(*gcodegen.last_position, scaled(z))}; gcode += gcodegen.travel_to( - *gcodegen.last_position, xy_point, ExtrusionRole::Mixed, comment, [](){return "";} + from, to, ExtrusionRole::Mixed, comment, [](){return "";} ); } else { gcode += gcodegen.writer().travel_to_xy(gcodegen.point_to_gcode(xy_point), comment); diff --git a/src/libslic3r/Geometry/ArcWelder.cpp b/src/libslic3r/Geometry/ArcWelder.cpp index 34e59834ba..840cf5397b 100644 --- a/src/libslic3r/Geometry/ArcWelder.cpp +++ b/src/libslic3r/Geometry/ArcWelder.cpp @@ -98,8 +98,6 @@ static bool foot_pt_on_segment(const Point &p1, const Point &p2, const Point &pt static inline bool circle_approximation_sufficient(const Circle &circle, const Points::const_iterator begin, const Points::const_iterator end, const double tolerance) { // The circle was calculated from the 1st and last point of the point sequence, thus the fitting of those points does not need to be evaluated. - assert(std::abs((*begin - circle.center).cast().norm() - circle.radius) < SCALED_EPSILON); - assert(std::abs((*std::prev(end) - circle.center).cast().norm() - circle.radius) < SCALED_EPSILON); assert(end - begin >= 3); // Test the 1st point. diff --git a/src/libslic3r/Geometry/ArcWelder.hpp b/src/libslic3r/Geometry/ArcWelder.hpp index 477f535eb4..0ce1444329 100644 --- a/src/libslic3r/Geometry/ArcWelder.hpp +++ b/src/libslic3r/Geometry/ArcWelder.hpp @@ -424,6 +424,9 @@ struct Segment // CCW or CW. Ignored for zero radius (linear segment). Orientation orientation{ Orientation::CCW }; + float height_fraction{ 1.f }; + float e_fraction{ 1.f }; + bool linear() const { return radius == 0; } bool ccw() const { return orientation == Orientation::CCW; } bool cw() const { return orientation == Orientation::CW; } diff --git a/tests/fff_print/CMakeLists.txt b/tests/fff_print/CMakeLists.txt index 2872aa464b..0be4a60452 100644 --- a/tests/fff_print/CMakeLists.txt +++ b/tests/fff_print/CMakeLists.txt @@ -20,6 +20,7 @@ add_executable(${_TEST_NAME}_tests test_seam_aligned.cpp test_seam_rear.cpp test_seam_random.cpp + test_seam_scarf.cpp benchmark_seams.cpp test_gcodefindreplace.cpp test_gcodewriter.cpp diff --git a/tests/fff_print/benchmark_seams.cpp b/tests/fff_print/benchmark_seams.cpp index 41f5242067..8eeeb9c120 100644 --- a/tests/fff_print/benchmark_seams.cpp +++ b/tests/fff_print/benchmark_seams.cpp @@ -105,13 +105,14 @@ TEST_CASE_METHOD(Slic3r::Test::SeamsFixture, "Seam benchmarks", "[Seams][.Benchm using namespace Slic3r; Placer placer; placer.init(print->objects(), params, [](){}); - std::vector> loops; + std::vector> loops; const PrintObject* object{print->objects().front()}; for (const Layer* layer :object->layers()) { for (const LayerSlice& lslice : layer->lslices_ex) { for (const LayerIsland &island : lslice.islands) { const LayerRegion &layer_region = *layer->get_region(island.perimeters.region()); + const PrintRegion ®ion = print->get_print_region(layer_region.region().print_region_id()); for (uint32_t perimeter_id : island.perimeters) { const auto *entity_collection{static_cast(layer_region.perimeters().entities[perimeter_id])}; if (entity_collection != nullptr) { @@ -120,7 +121,7 @@ TEST_CASE_METHOD(Slic3r::Test::SeamsFixture, "Seam benchmarks", "[Seams][.Benchm if (loop == nullptr) { continue; } - loops.emplace_back(layer, loop); + loops.emplace_back(layer, loop, ®ion); } } } @@ -129,8 +130,8 @@ TEST_CASE_METHOD(Slic3r::Test::SeamsFixture, "Seam benchmarks", "[Seams][.Benchm } BENCHMARK_ADVANCED("Place seam benchy")(Catch::Benchmark::Chronometer meter) { meter.measure([&] { - for (const auto &[layer, loop] : loops) { - placer.place_seam(layer, *loop, {0, 0}); + for (const auto &[layer, loop, region] : loops) { + placer.place_seam(layer, region, *loop, false, {0, 0}); } }); }; diff --git a/tests/fff_print/test_seam_geometry.cpp b/tests/fff_print/test_seam_geometry.cpp index a12fbdb3d6..76ff39d536 100644 --- a/tests/fff_print/test_seam_geometry.cpp +++ b/tests/fff_print/test_seam_geometry.cpp @@ -143,3 +143,30 @@ TEST_CASE("Calculate overhangs", "[Seams][SeamGeometry]") { 0.0, M_PI / 4.0, M_PI / 4.0, 0.0 })); } + +const Linesf lines{to_unscaled_linesf({ExPolygon{ + scaled(Vec2d{0.0, 0.0}), + scaled(Vec2d{1.0, 0.0}), + scaled(Vec2d{1.0, 1.0}), + scaled(Vec2d{0.0, 1.0}) +}})}; + +TEST_CASE("Offset along loop lines forward", "[Seams][SeamGeometry]") { + const std::optional result{Seams::Geometry::offset_along_loop_lines( + {0.5, 0.0}, 0, lines, 3.9, Seams::Geometry::Direction1D::forward + )}; + REQUIRE(result); + const auto &[point, line_index] = *result; + CHECK((scaled(point) - Point::new_scale(0.4, 0.0)).norm() < scaled(EPSILON)); + CHECK(line_index == 0); +} + +TEST_CASE("Offset along loop lines backward", "[Seams][SeamGeometry]") { + const std::optional result{Seams::Geometry::offset_along_loop_lines( + {1.0, 0.5}, 1, lines, 1.8, Seams::Geometry::Direction1D::backward + )}; + REQUIRE(result); + const auto &[point, line_index] = *result; + CHECK((scaled(point) - Point::new_scale(0.0, 0.3)).norm() < scaled(EPSILON)); + CHECK(line_index == 3); +} diff --git a/tests/fff_print/test_seam_scarf.cpp b/tests/fff_print/test_seam_scarf.cpp new file mode 100644 index 0000000000..d473ae7df9 --- /dev/null +++ b/tests/fff_print/test_seam_scarf.cpp @@ -0,0 +1,295 @@ +#include +#include +#include + +using namespace Slic3r; +using Seams::Scarf::Impl::PathPoint; + +TEST_CASE("Get path point", "[Seams][Scarf]") { + using Seams::Scarf::Impl::get_path_point; + const Points points{ + Point::new_scale(0, 0), + Point::new_scale(0, 1), + Point::new_scale(0, 2), + Point::new_scale(0, 3), + Point::new_scale(0, 4), + }; + const ExtrusionPaths paths{ + {{points[0], points[1]}, {}}, + {{points[1], points[2]}, {}}, + {{points[2], points[3], points[4]}, {}}, + }; + const std::size_t global_index{5}; // Index if paths are flattened. + const Point point{Point::new_scale(0, 3.5)}; + const PathPoint path_point{get_path_point(paths, point, global_index)}; + + CHECK(path_point.path_index == 2); + CHECK(path_point.previous_point_on_path_index == 1); + CHECK(path_point.point == point); +} + +TEST_CASE("Split path", "[Seams][Scarf]") { + using Seams::Scarf::Impl::split_path; + + const Points points{ + Point::new_scale(0, 0), + Point::new_scale(1, 0), + Point::new_scale(2, 0), + }; + + const auto split_point{Point::new_scale(1.5, 0)}; + + const ExtrusionPath path{Polyline{points}, {}}; + const auto[path_before, path_after]{split_path( + path, split_point, 1 + )}; + + REQUIRE(path_before.size() == 3); + CHECK(path_before.first_point() == points.front()); + CHECK(path_before.last_point() == split_point); + + REQUIRE(path_after.size() == 2); + CHECK(path_after.first_point() == split_point); + CHECK(path_after.last_point() == points.back()); +} + +TEST_CASE("Split paths", "[Seams][Scarf]") { + using Seams::Scarf::Impl::split_paths; + + const Points points{ + Point::new_scale(0, 0), + Point::new_scale(0, 1), + Point::new_scale(0, 2), + }; + ExtrusionPaths paths{ + {{points[0], points[1]}, {}}, + {{points[1], points[2]}, {}}, + }; + const auto split_point{Point::new_scale(0, 1.5)}; + PathPoint path_point{}; + path_point.point = split_point; + path_point.previous_point_on_path_index = 0; + path_point.path_index = 1; + const ExtrusionPaths result{split_paths(std::move(paths), path_point)}; + + REQUIRE(result.size() == 3); + CHECK(result[1].last_point() == split_point); + CHECK(result[2].first_point() == split_point); +} + +TEST_CASE("Get length", "[Seams][Scarf]") { + using Seams::Scarf::Impl::get_length; + using Seams::Scarf::Impl::convert_to_smooth; + + const Points points{ + Point::new_scale(0, 0), + Point::new_scale(0, 1), + Point::new_scale(0, 2.2), + }; + ExtrusionPaths paths{ + {{points[0], points[1]}, {}}, + {{points[1], points[2]}, {}}, + }; + + CHECK(get_length(convert_to_smooth(paths)) == scaled(2.2)); +} + +TEST_CASE("Linspace", "[Seams][Scarf]") { + using Seams::Scarf::Impl::linspace; + + const auto from{Point::new_scale(1, 0)}; + const auto to{Point::new_scale(3, 0)}; + + Points points{linspace(from, to, 3)}; + REQUIRE(points.size() == 3); + CHECK(points[1] == Point::new_scale(2, 0)); + + points = linspace(from, to, 5); + REQUIRE(points.size() == 5); + CHECK(points[1] == Point::new_scale(1.5, 0)); + CHECK(points[2] == Point::new_scale(2.0, 0)); + CHECK(points[3] == Point::new_scale(2.5, 0)); +} + +TEST_CASE("Ensure max distance", "[Seams][Scarf]") { + using Seams::Scarf::Impl::ensure_max_distance; + + const Points points{ + Point::new_scale(0, 0), + Point::new_scale(0, 1), + }; + + Points result{ensure_max_distance(points, scaled(0.5))}; + REQUIRE(result.size() == 3); + CHECK(result[1] == Point::new_scale(0, 0.5)); + + result = ensure_max_distance(points, scaled(0.49)); + REQUIRE(result.size() == 4); +} + +TEST_CASE("Lineary increase extrusion height", "[Seams][Scarf]") { + using Seams::Scarf::Impl::lineary_increase_extrusion_height; + using GCode::SmoothPath, GCode::SmoothPathElement; + + SmoothPath path{ + {{}, {{Point::new_scale(0, 0)}, {Point::new_scale(1, 0)}}}, + {{}, {{Point::new_scale(1, 0)}, {Point::new_scale(2, 0)}}}, + }; + + SmoothPath result{lineary_increase_extrusion_height(std::move(path), 0.5)}; + + CHECK(result[0].path[0].height_fraction == Approx(0.5)); + CHECK(result[0].path[0].e_fraction == Approx(0.0)); + CHECK(result[0].path[1].height_fraction == Approx(0.75)); + CHECK(result[0].path[1].e_fraction == Approx(0.5)); + CHECK(result[1].path[0].height_fraction == Approx(0.75)); + CHECK(result[1].path[0].e_fraction == Approx(0.5)); + CHECK(result[1].path[1].height_fraction == Approx(1.0)); + CHECK(result[1].path[1].e_fraction == Approx(1.0)); +} + +TEST_CASE("Lineary reduce extrusion amount", "[Seams][Scarf]") { + using Seams::Scarf::Impl::lineary_readuce_extrusion_amount; + using GCode::SmoothPath, GCode::SmoothPathElement; + + SmoothPath path{ + {{}, {{Point::new_scale(0, 0)}, {Point::new_scale(1, 0)}}}, + {{}, {{Point::new_scale(1, 0)}, {Point::new_scale(2, 0)}}}, + }; + + SmoothPath result{lineary_readuce_extrusion_amount(std::move(path))}; + + CHECK(result[0].path[0].e_fraction == Approx(1.0)); + CHECK(result[0].path[1].e_fraction == Approx(0.5)); + CHECK(result[1].path[0].e_fraction == Approx(0.5)); + CHECK(result[1].path[1].e_fraction == Approx(0.0)); +} + +TEST_CASE("Elevate scarf", "[Seams][Scarf]") { + using Seams::Scarf::Impl::elevate_scarf; + using Seams::Scarf::Impl::convert_to_smooth; + + + const Points points{ + Point::new_scale(0, 0), + Point::new_scale(1, 0), + Point::new_scale(2, 0), + Point::new_scale(3, 0), + }; + const ExtrusionPaths paths{ + {{points[0], points[1]}, {}}, + {{points[1], points[2]}, {}}, + {{points[2], points[3]}, {}}, + }; + + const GCode::SmoothPath result{elevate_scarf( + paths, + 1, + convert_to_smooth, + 0.5 + )}; + + + REQUIRE(result.size() == 3); + REQUIRE(result[0].path.size() == 2); + CHECK(result[0].path[0].e_fraction == Approx(0.0)); + CHECK(result[0].path[0].height_fraction == Approx(0.5)); + CHECK(result[0].path[1].e_fraction == Approx(1.0)); + CHECK(result[0].path[1].height_fraction == Approx(1.0)); + REQUIRE(result[1].path.size() == 2); + CHECK(result[1].path[0].e_fraction == Approx(1.0)); + CHECK(result[1].path[0].height_fraction == Approx(1.0)); + CHECK(result[1].path[1].e_fraction == Approx(1.0)); + CHECK(result[1].path[1].height_fraction == Approx(1.0)); + REQUIRE(result[2].path.size() == 2); + CHECK(result[2].path[0].e_fraction == Approx(1.0)); + CHECK(result[2].path[0].height_fraction == Approx(1.0)); + CHECK(result[2].path[1].e_fraction == Approx(0.0)); + CHECK(result[2].path[1].height_fraction == Approx(1.0)); +} + +TEST_CASE("Get point offset from the path end", "[Seams][Scarf]") { + using Seams::Scarf::Impl::get_point_offset_from_end; + + const Points points{ + Point::new_scale(0, 0), + Point::new_scale(1, 0), + Point::new_scale(2, 0), + Point::new_scale(3, 0), + }; + const ExtrusionPaths paths{ + {{points[0], points[1]}, {}}, + {{points[1], points[2]}, {}}, + {{points[2], points[3]}, {}}, + }; + + std::optional result{get_point_offset_from_end(paths, scaled(1.6))}; + + REQUIRE(result); + CHECK(result->point == Point::new_scale(1.4, 0)); + CHECK(result->previous_point_on_path_index == 0); + CHECK(result->path_index == 1); +} + +TEST_CASE("Add scarf seam", "[Seams][Scarf]") { + using Seams::Scarf::add_scarf_seam; + using Seams::Scarf::Impl::convert_to_smooth; + using Seams::Scarf::Impl::get_length; + using Seams::Scarf::Scarf; + + const Points points{ + Point::new_scale(0, 0), + Point::new_scale(1, 0), + Point::new_scale(1, 1), + Point::new_scale(0, 1), + Point::new_scale(0, 0), + }; + const ExtrusionPaths paths{ + {Polyline{points}, {}}, + }; + Scarf scarf{}; + scarf.end_point = Point::new_scale(1, 0.5); + scarf.start_height = 0.2; + scarf.length = 1.0; + scarf.max_segment_length = 0.1; + scarf.end_point_previous_index = 1; + scarf.entire_loop = false; + + const auto start_point{Point::new_scale(0.5, 0)}; + + const auto [path, wipe_offset]{add_scarf_seam(ExtrusionPaths{paths}, scarf, convert_to_smooth, false)}; + + REQUIRE(path.size() == 4); + CHECK(wipe_offset == 1); + + REQUIRE(path.back().path.size() >= scarf.length / scarf.max_segment_length); + CHECK(path.back().path.back().point == scarf.end_point); + CHECK(path.back().path.front().point == start_point); + CHECK(path.back().path.back().e_fraction == Approx(0)); + + REQUIRE(path.front().path.size() >= scarf.length / scarf.max_segment_length); + CHECK(path.front().path.back().point == scarf.end_point); + CHECK(path.front().path.front().point == start_point); + CHECK(path.front().path.front().e_fraction == Approx(0)); + CHECK(path.front().path.front().height_fraction == Approx(scarf.start_height)); + + CHECK(path.front().path[5].point == points[1]); + CHECK(path.front().path[5].e_fraction == Approx(0.5)); + CHECK(path.front().path[5].height_fraction == Approx(0.6)); + CHECK(path.back().path[5].e_fraction == Approx(0.5)); + CHECK(path.back().path[5].height_fraction == Approx(1.0)); + + scarf.entire_loop = true; + const auto [loop_path, _]{add_scarf_seam(ExtrusionPaths{paths}, scarf, convert_to_smooth, false)}; + + CHECK(get_length(loop_path) == scaled(8.0)); + REQUIRE(!loop_path.empty()); + REQUIRE(!loop_path.front().path.empty()); + CHECK(loop_path.front().path.front().point == scarf.end_point); + CHECK(loop_path.front().path.front().e_fraction == Approx(0)); + REQUIRE(!loop_path.back().path.empty()); + CHECK(loop_path.back().path.back().point == scarf.end_point); + + CHECK(loop_path.front().path.at(20).e_fraction == Approx(0.5)); + CHECK(loop_path.front().path.at(20).point == Point::new_scale(0, 0.5)); +}