diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index bafb79d0fa..91bab8e93d 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2172,6 +2172,7 @@ LayerResult GCodeGenerator::process_layer( + float_to_string_decimal_point(height) + "\n"; // update caches + const coordf_t previous_layer_z{m_last_layer_z}; m_last_layer_z = static_cast(print_z); m_max_layer_z = std::max(m_max_layer_z, m_last_layer_z); m_last_height = height; @@ -2186,7 +2187,7 @@ LayerResult GCodeGenerator::process_layer( print.config().before_layer_gcode.value, m_writer.extruder()->id(), &config) + "\n"; } - gcode += this->change_layer(print_z); // this will increase m_layer_index + gcode += this->change_layer(previous_layer_z, print_z); // this will increase m_layer_index m_layer = &layer; m_object_layer_over_raft = false; if (! print.config().layer_gcode.value.empty()) { @@ -2608,22 +2609,143 @@ std::string GCodeGenerator::preamble() return gcode; } +namespace GCode::Impl { +Polygon generate_regular_polygon( + const Point& centroid, + const Point& start_point, + const unsigned points_count +) { + Points points; + points.reserve(points_count); + const double part_angle{2*M_PI / points_count}; + for (unsigned i = 0; i < points_count; ++i) { + const double current_angle{i * part_angle}; + points.emplace_back(scaled(std::cos(current_angle)), scaled(std::sin(current_angle))); + } + + Polygon regular_polygon{points}; + const Vec2d current_vector{unscaled(regular_polygon.points.front())}; + const Vec2d expected_vector{unscaled(start_point) - unscaled(centroid)}; + + const double current_scale = current_vector.norm(); + const double expected_scale = expected_vector.norm(); + regular_polygon.scale(expected_scale / current_scale); + + regular_polygon.rotate(angle(current_vector, expected_vector)); + + regular_polygon.translate(centroid); + + return regular_polygon; +} + +Bed::Bed(const std::vector& shape, const double padding): + inner_offset(get_inner_offset(shape, padding)), + centroid(unscaled(inner_offset.centroid())) +{} + +bool Bed::contains_within_padding(const Vec2d& point) const { + return inner_offset.contains(scaled(point)); +} + +Polygon Bed::get_inner_offset(const std::vector& shape, const double padding) { + Points shape_scaled; + shape_scaled.reserve(shape.size()); + using std::begin, std::end, std::back_inserter, std::transform; + transform(begin(shape), end(shape), back_inserter(shape_scaled), [](const Vec2d& point){ + return scaled(point); + }); + return shrink({Polygon{shape_scaled}}, scaled(padding)).front(); +} +} + +std::optional GCodeGenerator::get_helical_layer_change_gcode( + const coordf_t previous_layer_z, + const coordf_t print_z, + const std::string& comment +) { + + if (!this->last_pos_defined()) { + return std::nullopt; + } + + const double circle_radius{2}; + const unsigned n_gon_points_count{16}; + + const Point n_gon_start_point{this->last_pos()}; + + static GCode::Impl::Bed bed{ + this->m_config.bed_shape.values, + circle_radius + }; + if (!bed.contains_within_padding(this->point_to_gcode(n_gon_start_point))) { + return std::nullopt; + } + + const Point n_gon_centeroid{ + n_gon_start_point + + scaled(Vec2d{ + (bed.centroid - unscaled(n_gon_start_point)).normalized() + * circle_radius + }) + }; + + const Polygon n_gon{GCode::Impl::generate_regular_polygon( + n_gon_centeroid, + n_gon_start_point, + n_gon_points_count + )}; + + const double n_gon_circumference = unscaled(n_gon.length()); + + const double z_change{print_z - previous_layer_z}; + Points3 helix{GCode::Impl::generate_elevated_travel( + n_gon.points, + {}, + previous_layer_z, + [&](const double distance){ + return distance / n_gon_circumference * z_change; + } + )}; + + helix.emplace_back(to_3d(this->last_pos(), scaled(print_z))); + + return this->generate_travel_gcode(helix, comment); +} + // called by GCodeGenerator::process_layer() -std::string GCodeGenerator::change_layer(coordf_t print_z) +std::string GCodeGenerator::change_layer(coordf_t previous_layer_z, coordf_t print_z) { std::string gcode; if (m_layer_count > 0) // Increment a progress bar indicator. gcode += m_writer.update_progress(++ m_layer_index, m_layer_count); - coordf_t z = print_z + m_config.z_offset.value; // in unscaled coordinates + if (EXTRUDER_CONFIG(retract_layer_change)) gcode += this->retract_and_wipe(); - { - std::ostringstream comment; - comment << "move to next layer (" << m_layer_index << ")"; - gcode += m_writer.travel_to_z(z, comment.str()); - } + const std::string comment{"move to next layer (" + std::to_string(m_layer_index) + ")"}; + + bool helical_layer_change{ + (!this->m_spiral_vase || !this->m_spiral_vase->is_enabled()) + && print_z > previous_layer_z + && EXTRUDER_CONFIG(travel_lift_before_obstacle) + && EXTRUDER_CONFIG(travel_slope) > 0 && EXTRUDER_CONFIG(travel_slope) < 90 + }; + + const std::optional helix_gcode{ + helical_layer_change ? + this->get_helical_layer_change_gcode( + m_config.z_offset.value + previous_layer_z, + m_config.z_offset.value + print_z, + comment + ) : + std::nullopt + }; + gcode += ( + helix_gcode ? + *helix_gcode : + m_writer.travel_to_z(m_config.z_offset.value + print_z, comment) + ); // forget last wiping path as wiping after raising Z is pointless m_wipe.reset_path(); diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index efa75fcd67..71c5c1ff13 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -135,6 +135,39 @@ Points3 generate_elevated_travel( const std::function& elevation ); +/** + * Generates a regular polygon - all angles are the same (e.g. typical hexagon). + * + * @param centroid Central point. + * @param start_point The polygon point are ordered. This is the first point. + * @param points_count Amount of nodes of the polygon (e.g. 6 for haxagon). + * + * Distance between centroid and start point sets the scale of the polygon. + */ +Polygon generate_regular_polygon( + const Point& centroid, + const Point& start_point, + const unsigned points_count +); + +class Bed { + private: + Polygon inner_offset; + static Polygon get_inner_offset(const std::vector& shape, const double padding); + + public: + /** + * Bed shape with inner padding. + */ + Bed(const std::vector& shape, const double padding); + + Vec2d centroid; + + /** + * Returns true if the point is within the bed shape including inner padding. + */ + bool contains_within_padding(const Vec2d& point) const; +}; } class GCodeGenerator { @@ -302,7 +335,12 @@ private: bool last_pos_defined() const { return m_last_pos_defined; } void set_extruders(const std::vector &extruder_ids); std::string preamble(); - std::string change_layer(coordf_t print_z); + std::optional get_helical_layer_change_gcode( + const coordf_t previous_layer_z, + const coordf_t print_z, + const std::string& comment + ); + std::string change_layer(coordf_t previous_layer_z, 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_loop(const ExtrusionLoop &loop, 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, diff --git a/src/libslic3r/GCode/SpiralVase.hpp b/src/libslic3r/GCode/SpiralVase.hpp index 19bc3f72e5..ac1543f21c 100644 --- a/src/libslic3r/GCode/SpiralVase.hpp +++ b/src/libslic3r/GCode/SpiralVase.hpp @@ -27,8 +27,12 @@ public: m_enabled = en; } + bool is_enabled() const { + return m_enabled; + } + std::string process_layer(const std::string &gcode); - + private: const PrintConfig &m_config; GCodeReader m_reader; diff --git a/tests/fff_print/test_gcode.cpp b/tests/fff_print/test_gcode.cpp index bf68a6d424..f8d33e1461 100644 --- a/tests/fff_print/test_gcode.cpp +++ b/tests/fff_print/test_gcode.cpp @@ -139,3 +139,53 @@ TEST_CASE("Generate elevated travel", "[GCode]") { scaled(Vec3f{1, 0, 4.0}) }); } + +TEST_CASE("Generate regular polygon", "[GCode]") { + const unsigned points_count{32}; + const Point centroid{scaled(Vec2d{5, -2})}; + const Polygon result{generate_regular_polygon(centroid, scaled(Vec2d{0, 0}), points_count)}; + const Point oposite_point{centroid * 2}; + + REQUIRE(result.size() == 32); + CHECK(result[16].x() == Approx(oposite_point.x())); + CHECK(result[16].y() == Approx(oposite_point.y())); + + std::vector angles; + angles.reserve(points_count); + for (unsigned index = 0; index < points_count; index++) { + const unsigned previous_index{index == 0 ? points_count - 1 : index - 1}; + const unsigned next_index{index == points_count - 1 ? 0 : index + 1}; + + const Point previous_point = result.points[previous_index]; + const Point current_point = result.points[index]; + const Point next_point = result.points[next_index]; + + angles.emplace_back(angle(Vec2crd{previous_point - current_point}, Vec2crd{next_point - current_point})); + } + + std::vector expected; + angles.reserve(points_count); + std::generate_n(std::back_inserter(expected), points_count, [&](){ + return angles.front(); + }); + + CHECK_THAT(angles, Catch::Matchers::Approx(expected)); +} + +TEST_CASE("Square bed with padding", "[GCode]") { + const Bed bed{ + { + Vec2d{0, 0}, + Vec2d{100, 0}, + Vec2d{100, 100}, + Vec2d{0, 100} + }, + 10.0 + }; + + CHECK(bed.centroid.x() == 50); + CHECK(bed.centroid.y() == 50); + CHECK(bed.contains_within_padding(Vec2d{10, 10})); + CHECK_FALSE(bed.contains_within_padding(Vec2d{9, 10})); + +}