From da401d661e78b9f534f095c52acbe40364c5122d Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 16 Aug 2024 14:18:55 +0200 Subject: [PATCH 1/6] Integrate gradual flow code CURA-12096 --- include/LayerPlan.h | 2 + include/gradual_flow/boost_tags.h | 135 ++++++++ include/gradual_flow/concepts.h | 127 +++++++ include/gradual_flow/gcode_path.h | 451 +++++++++++++++++++++++++ include/gradual_flow/point_container.h | 134 ++++++++ include/gradual_flow/processor.h | 100 ++++++ include/gradual_flow/utils.h | 85 +++++ src/FffGcodeWriter.cpp | 2 + src/LayerPlan.cpp | 11 +- 9 files changed, 1045 insertions(+), 2 deletions(-) create mode 100644 include/gradual_flow/boost_tags.h create mode 100644 include/gradual_flow/concepts.h create mode 100644 include/gradual_flow/gcode_path.h create mode 100644 include/gradual_flow/point_container.h create mode 100644 include/gradual_flow/processor.h create mode 100644 include/gradual_flow/utils.h diff --git a/include/LayerPlan.h b/include/LayerPlan.h index d81bb9c437..7833e0c8e9 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -778,6 +778,8 @@ class LayerPlan : public NoCopy */ void applyBackPressureCompensation(); + void applyGradualFlow(); + private: /*! * \brief Compute the preferred or minimum combing boundary diff --git a/include/gradual_flow/boost_tags.h b/include/gradual_flow/boost_tags.h new file mode 100644 index 0000000000..9e2273acd5 --- /dev/null +++ b/include/gradual_flow/boost_tags.h @@ -0,0 +1,135 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef GRADUAL_FLOW_BOOST_TAGS_H +#define GRADUAL_FLOW_BOOST_TAGS_H + + +#ifndef INFILL_BOOST_TAGS_H +#define INFILL_BOOST_TAGS_H + +#include +#include + +#include +#include +#include +#include +#include + +#include "gradual_flow/concepts.h" +#include "gradual_flow/point_container.h" + +namespace boost::geometry::traits +{ + +template<> +struct tag +{ + using type = point_tag; +}; + +template<> +struct dimension : boost::mpl::int_<2> +{ +}; + +template<> +struct coordinate_type +{ + using type = ClipperLib::cInt; +}; + +template<> +struct coordinate_system +{ + using type = boost::geometry::cs::cartesian; +}; + +template +struct access +{ + static_assert(Index < 2, "Out of range"); + using Point = ClipperLib::IntPoint; + using CoordinateType = typename coordinate_type::type; + constexpr static inline CoordinateType get(Point const& p) noexcept + { + return Index == 0 ? p.X : p.Y; + } + + constexpr static inline void set(Point& p, CoordinateType const& value) noexcept + { + if (Index == 0) + { + p.X = value; + } + else + { + p.Y = value; + } + } +}; + +template<> +struct tag> +{ + using type = linestring_tag; +}; + +template<> +struct point_order> +{ + static const order_selector value = clockwise; +}; + +template<> +struct closure> +{ + static const closure_selector value = open; +}; + +template<> +struct tag> +{ + using type = ring_tag; +}; + +template<> +struct point_order> +{ + static const order_selector value = counterclockwise; +}; + +template<> +struct closure> +{ + static const closure_selector value = open; +}; + +template<> +struct tag> +{ + using type = ring_tag; +}; + +template<> +struct point_order +{ + static const order_selector value = counterclockwise; +}; + +template<> +struct closure +{ + static const closure_selector value = open; +}; + +template<> +struct tag +{ + using type = ring_tag; +}; + +} // namespace boost::geometry::traits + +#endif // GRADUAL_FLOW_BOOST_TAGS_H diff --git a/include/gradual_flow/concepts.h b/include/gradual_flow/concepts.h new file mode 100644 index 0000000000..923157ada7 --- /dev/null +++ b/include/gradual_flow/concepts.h @@ -0,0 +1,127 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef GRADUAL_FLOW_CONCEPTS_H +#define GRADUAL_FLOW_CONCEPTS_H + +#include + +#if __has_include() +#include +#elif __has_include() +#include +#define USE_EXPERIMENTAL_CONCEPTS +#endif + +#include +#include + +namespace cura::gradual_flow +{ +enum class direction +{ + NA, + CW, + CCW +}; + +namespace concepts +{ + +template +concept closable = requires(T t) +{ + requires ranges::convertible_to; +}; + +template +concept is_closed_point_container = closable && requires(T t) +{ + t.is_closed == true; +}; + +template +concept is_open_point_container = closable && requires(T t) +{ + t.is_closed == false; +}; + +template +concept directional = requires(T t) +{ + requires std::is_same_v; +}; + +template +concept is_clockwise_point_container = directional && requires(T t) +{ + t.winding == direction::CW; +}; + +template +concept is_counterclockwise_point_container = directional && requires(T t) +{ + t.winding == direction::CCW; +}; + +template +concept point2d_named = requires(T point) +{ + point.X; + point.Y; +}; + +/*! + * @brief A 2D point, defined either as a named object with X and Y attributes, or as a range of two integral values. + * @details This concept is used to check if a type is a 2D point. A 2D point is a type that has a X and Y member or a type that is a range of integral types with a size of 2. + * @tparam T Type to check + */ +template +concept point2d = point2d_named ||(ranges::range&& ranges::integral&& std::tuple_size_v == 2); + +template +concept point3d_named = requires(T point) +{ + point.x; + point.y; + point.z; +}; + +/*! + * @brief A 3D point, defined either as a named object with x, y, and z attributes, or as a range of three integral values. + * @details This concept is used to check if a type is a 3D point. A 3D point is a type that has a x, y and z member or a type that is a range of integral types with a size of 3. + * @tparam T Type to check + */ +template +concept point3d = point3d_named ||(ranges::range&& ranges::integral&& std::tuple_size_v == 3); + +template +concept point_named = point2d_named || point3d_named; + +/*! + * @brief Either a Point2D or a Point3D + * @details This concept is used to check if a type is a point. A point is a type that is a 2D or 3D point. + * @tparam T Type to check + */ +template +concept point = point2d || point3d; + +template +concept point_ranged = point && ! point2d_named && ! point3d_named; + +template +concept polyline = ranges::range && is_open_point_container && point; + +template +concept polygon = ranges::range && is_closed_point_container && point; + +template +concept polygons = ranges::range && polygon; + +template +concept poly_range = polygon || polyline; + +} // namespace concepts +} // namespace cura::gradual_flow + +#endif // GRADUAL_FLOW_CONCEPTS_H diff --git a/include/gradual_flow/gcode_path.h b/include/gradual_flow/gcode_path.h new file mode 100644 index 0000000000..56c278dbf0 --- /dev/null +++ b/include/gradual_flow/gcode_path.h @@ -0,0 +1,451 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef GRADUAL_FLOW_GCODE_PATH_H +#define GRADUAL_FLOW_GCODE_PATH_H + +#include +#include + +#include +#include +#include +#include +#include + +#include "gradual_flow/point_container.h" +#include "gradual_flow/utils.h" +#include "pathPlanning/GCodePath.h" + +namespace cura::gradual_flow +{ + +enum class FlowState +{ + STABLE, + TRANSITION, + UNDEFINED +}; + +struct GCodePath +{ + const cura::GCodePath* original_gcode_path_data; + geometry::polyline<> points; + double speed{ targetSpeed() }; // um/s + double flow_{ extrusionVolumePerMm() * speed }; // um/s + double total_length{ totalLength() }; // um + + double targetSpeed() const // um/s + { + return original_gcode_path_data->config.speed_derivatives.speed * original_gcode_path_data->speed_factor * 1e3; + } + + /* + * Returns if the path is a travel move. + * + * @return `true` If the path is a travel move, `false` otherwise + */ + bool isTravel() const + { + return targetFlow() <= 0; + } + + /* + * Returns if the path is a retract move. + * + * @return `true` If the path is a retract move, `false` otherwise + */ + bool isRetract() const + { + return original_gcode_path_data->retract; + } + + /* + * Returns the extrusion volume per um of the path. + * + * @return The extrusion volume per um of the path in um^3/um + */ + double extrusionVolumePerMm() const // um^3/um + { + return original_gcode_path_data->flow * original_gcode_path_data->config.line_width * original_gcode_path_data->config.layer_thickness * original_gcode_path_data->flow; + } + + /* + * Returns the extrusion volume of the path. + * + * @return The extrusion volume of the path in um^3/s + */ + double flow() const // um^3/s + { + return flow_; + } + + /* + * Returns the target extrusion volume of the path. + * + * @return The target extrusion volume of the path in um^3/s + */ + double targetFlow() const // um^3/s + { + return extrusionVolumePerMm() * targetSpeed(); + } + + /* + * Returns the path as an SVG path data string. + * + * @return the SVG path data string + */ + std::string toSvgPathData() const + { + std::string path_data; + auto is_first_point = true; + for (auto point : points) + { + const auto identifier = is_first_point ? "M" : "L"; + path_data += fmt::format("{}{} {} ", identifier, point.X * 1e-3, point.Y * 1e-3); + is_first_point = false; + } + return path_data; + } + + /* + * Returns the path as an SVG path-element. + * + * @return the SVG path + */ + std::string toSvgPath() + { + const auto path_data = toSvgPathData(); + + if (isTravel()) + { + return fmt::format("", path_data); + } + + const auto [r, g, b] = gradual_flow::utils::hsvToRgb(flow() * .00000003, 100., 100.); + const auto color = fmt::format("rgb({},{},{})", r, g, b); + return fmt::format("", path_data, color); + } + + /* + * Returns the total length of the path. + * + * @return the length in um + */ + double totalLength() const // um + { + double path_length = 0; + auto last_point = points.front(); + for (const auto& point : points | ranges::views::drop(1)) + { + path_length += std::hypot(point.X - last_point.X, point.Y - last_point.Y); + last_point = point; + } + return path_length; + } + + /* + * Returns the total duration of the path. + * + * @return the duration in seconds + */ + double totalDuration() const // s + { + return total_length / speed; + } + + /* + * Splits either the beginning or the end of the path into a new path. + * + * @param partition_duration duration of the partitioned paths in s + * @param partition_speed speed of the partitioned paths in um/s + * @param direction + * @return a tuple of the partitioned path and the remaining path, the + * remaining path can possibly be empty if the duration of the + * partitioned path is equal or longer than the duration of the original + * path + */ + std::tuple, double> partition(const double partition_duration, const double partition_speed, const utils::Direction direction) const + { + const auto total_path_duration = total_length / partition_speed; + if (partition_duration >= total_path_duration) + { + const auto remaining_partition_duration = partition_duration - total_path_duration; + const GCodePath gcode_path{ .original_gcode_path_data = original_gcode_path_data, .points = points, .speed = partition_speed }; + return std::make_tuple(gcode_path, std::nullopt, remaining_partition_duration); + } + + auto current_partition_duration = 0.0; + auto partition_index = direction == utils::Direction::Forward ? 0 : points.size() - 1; + auto iteration_direction = direction == utils::Direction::Forward ? 1 : -1; + auto prev_point = points[partition_index]; + + while (true) + { + const auto next_point = points[partition_index + iteration_direction]; + const auto segment_length = std::hypot(next_point.X - prev_point.X, next_point.Y - prev_point.Y); + const auto segment_duration = segment_length / partition_speed; + + if (current_partition_duration + segment_duration < partition_duration) + { + prev_point = next_point; + current_partition_duration += segment_duration; + partition_index += iteration_direction; + } + else + { + const auto duration_left = partition_duration - current_partition_duration; + auto segment_ratio = duration_left / segment_duration; + assert(segment_ratio >= -1e-6 && segment_ratio <= 1. + 1e-6); + const auto partition_x = prev_point.X + static_cast(static_cast(next_point.X - prev_point.X) * segment_ratio); + const auto partition_y = prev_point.Y + static_cast(static_cast(next_point.Y - prev_point.Y) * segment_ratio); + const auto partition_point = ClipperLib::IntPoint(partition_x, partition_y); + + /* + * partition point + * v + * 0---------1---------2----x------3---------4 + * ^ ^ + * partition index when partition_index when + * going forwards going backwards + * + * When we partition the path in a "left" and "right" path we + * expect we end up with the same path for the same partition + * if we go forwards or backwards. This is why + * partition_point_index = partition_index + 1 + * when going _forwards_, while going _backwards_ it is equal + * to + * partition_point_index = partition_index + * + * Given this new index every point for which + * 0 >= i > partition_point_index + * holds belongs to the _left_ path while every point for which + * partition_point_index >= i > points.size() + * belongs to the right path. + */ + const auto partition_point_index = direction == utils::Direction::Forward ? partition_index + 1 : partition_index; + + // points left of the partition_index + geometry::polyline<> left_points; + for (unsigned int i = 0; i < partition_point_index; ++i) + { + left_points.emplace_back(points[i]); + } + left_points.emplace_back(partition_point); + + // points right of the partition_index + geometry::polyline<> right_points; + right_points.emplace_back(partition_point); + for (unsigned int i = partition_point_index; i < points.size(); ++i) + { + right_points.emplace_back(points[i]); + } + + switch (direction) + { + case utils::Direction::Forward: + { + const GCodePath partition_gcode_path{ + .original_gcode_path_data = original_gcode_path_data, + .points = left_points, + .speed = partition_speed, + }; + const GCodePath remaining_gcode_path{ + .original_gcode_path_data = original_gcode_path_data, + .points = right_points, + .speed = speed, + }; + return std::make_tuple(partition_gcode_path, remaining_gcode_path, .0); + }; + case utils::Direction::Backward: + { + const GCodePath partition_gcode_path{ + .original_gcode_path_data = original_gcode_path_data, + .points = right_points, + .speed = partition_speed, + }; + const GCodePath remaining_gcode_path{ + .original_gcode_path_data = original_gcode_path_data, + .points = left_points, + .speed = speed, + }; + return std::make_tuple(partition_gcode_path, remaining_gcode_path, .0); + } + } + } + } + } + + cura::GCodePath toClassicPath(const bool include_first_point) const + { + cura::GCodePath output_path = *original_gcode_path_data; + + output_path.points.clear(); + for (auto& point : points | ranges::views::drop(include_first_point ? 0 : 1)) + { + output_path.points.push_back(point); + } + + output_path.config.speed_derivatives.speed = speed * 1e-3; + + return output_path; + } +}; + +struct GCodeState +{ + double current_flow{ 0.0 }; // um^3/s + double flow_acceleration{ 0.0 }; // um^3/s^2 + double flow_deceleration{ 0.0 }; // um^3/s^2 + double discretized_duration{ 0.0 }; // s + double discretized_duration_remaining{ 0.0 }; // s + double target_end_flow{ 0.0 }; // um^3/s + double reset_flow_duration{ 0.0 }; // s + FlowState flow_state{ FlowState::UNDEFINED }; + + std::vector processGcodePaths(const std::vector& gcode_paths) + { + // reset the discretized_duration_remaining + discretized_duration_remaining = 0; + + std::vector forward_pass_gcode_paths; + for (auto& gcode_path : gcode_paths) + { + auto discretized_paths = processGcodePath(gcode_path, gradual_flow::utils::Direction::Forward); + for (auto& path : discretized_paths) + { + forward_pass_gcode_paths.emplace_back(path); + } + } + + // reset the discretized_duration_remaining + discretized_duration_remaining = 0; + + // set the current flow to the target end flow. When executing the backward pass we want to + // we start with this flow and gradually increase it to the target flow. However, if the + // highest flow we can achieve is lower than this target flow we want to use that flow + // instead. + current_flow = std::min(current_flow, target_end_flow); + + std::list backward_pass_gcode_paths; + for (auto& gcode_path : forward_pass_gcode_paths | ranges::views::reverse) + { + auto discretized_paths = processGcodePath(gcode_path, gradual_flow::utils::Direction::Backward); + for (auto& path : discretized_paths) + { + backward_pass_gcode_paths.emplace_front(path); + } + } + + return std::vector(backward_pass_gcode_paths.begin(), backward_pass_gcode_paths.end()); + } + + /* + * Discretizes a GCodePath into multiple GCodePaths with a gradual increase in flow. + * + * @param path the path to discretize + * + * @return a vector of discretized paths with a gradual increase in flow + */ + std::vector processGcodePath(const GCodePath& path, const utils::Direction direction) + { + if (path.isTravel()) + { + if (path.isRetract() || path.totalDuration() > reset_flow_duration) + { + flow_state = FlowState::UNDEFINED; + } + return { path }; + } + + // After a long travel move we want to reset the flow to the target end flow + if (flow_state == FlowState::UNDEFINED && direction == utils::Direction::Forward) + { + current_flow = path.targetFlow(); + } + + auto target_flow = path.flow(); + if (target_flow <= current_flow) + { + current_flow = target_flow; + discretized_duration_remaining = 0; + flow_state = FlowState::STABLE; + return { path }; + } + + const auto extrusion_volume_per_mm = path.extrusionVolumePerMm(); // um^3/um + + std::vector discretized_paths; + + GCodePath remaining_path = path; + + if (discretized_duration_remaining > 0.) + { + const auto discretized_segment_speed = current_flow / extrusion_volume_per_mm; // um^3/s / um^3/um = um/s + const auto [partitioned_gcode_path, new_remaining_path, remaining_partition_duration] + = path.partition(discretized_duration_remaining, discretized_segment_speed, direction); + discretized_duration_remaining = std::max(.0, discretized_duration_remaining - remaining_partition_duration); + if (new_remaining_path.has_value()) + { + remaining_path = new_remaining_path.value(); + discretized_paths.emplace_back(partitioned_gcode_path); + } + else + { + flow_state = FlowState::TRANSITION; + return { partitioned_gcode_path }; + } + } + + // while we have not reached the target flow, iteratively discretize the path + // such that the new path has a duration of discretized_duration and with each + // iteration an increased flow of flow_acceleration + while (current_flow < target_flow) + { + const auto flow_delta = (direction == utils::Forward ? flow_acceleration : flow_deceleration) * discretized_duration; + current_flow = std::min(target_flow, current_flow + flow_delta); + + const auto segment_speed = current_flow / extrusion_volume_per_mm; // um^3/s / um^3/um = um/s + + if (current_flow == target_flow) + { + remaining_path.speed = segment_speed; + discretized_duration_remaining = std::max(discretized_duration_remaining - remaining_path.totalDuration(), .0); + flow_state = discretized_duration_remaining > 0. ? FlowState::TRANSITION : FlowState::STABLE; + discretized_paths.emplace_back(remaining_path); + return discretized_paths; + } + + const auto [partitioned_gcode_path, new_remaining_path, remaining_partition_duration] = remaining_path.partition(discretized_duration, segment_speed, direction); + + // when we have remaining paths, we should have no remaining duration as the + // remaining duration should then be consumed by the remaining paths + assert(! new_remaining_path.has_value() || remaining_partition_duration == 0); + // having no remaining paths implies that there is a duration remaining that should be consumed + // by the next path + assert(new_remaining_path.has_value() || remaining_partition_duration > 0); + + discretized_paths.emplace_back(partitioned_gcode_path); + + if (new_remaining_path.has_value()) + { + remaining_path = new_remaining_path.value(); + } + else + { + flow_state = FlowState::TRANSITION; + discretized_duration_remaining = remaining_partition_duration; + return discretized_paths; + } + } + discretized_paths.emplace_back(remaining_path); + + flow_state = discretized_duration_remaining > 0. ? FlowState::TRANSITION : FlowState::STABLE; + + return discretized_paths; + } +}; + +} // namespace cura::gradual_flow + +#endif // GRADUAL_FLOW_GCODE_PATH_H diff --git a/include/gradual_flow/point_container.h b/include/gradual_flow/point_container.h new file mode 100644 index 0000000000..ae070eeddc --- /dev/null +++ b/include/gradual_flow/point_container.h @@ -0,0 +1,134 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef GRADUAL_FLOW_POINT_CONTAINER_H +#define GRADUAL_FLOW_POINT_CONTAINER_H + +#include +#include +#include +#include + +#include +#include + +#include "gradual_flow/concepts.h" + +namespace cura::gradual_flow::geometry +{ + +using Point = ClipperLib::IntPoint; + +/*! The base clase of all point based container types + * + * @tparam P + * @tparam IsClosed + * @tparam Direction + * @tparam Container + */ +template +struct point_container : public std::vector

+{ + inline static constexpr bool is_closed = IsClosed; + inline static constexpr direction winding = Direction; + + constexpr point_container() noexcept = default; + constexpr point_container(std::initializer_list

points) noexcept + : std::vector

(points) + { + } +}; + +template +struct polyline : public point_container +{ + constexpr polyline() noexcept = default; + constexpr polyline(std::initializer_list

points) noexcept + : point_container(points) + { + } +}; + +template +struct polygon : public point_container +{ + constexpr polygon() noexcept = default; + constexpr polygon(std::initializer_list

points) noexcept + : point_container(points) + { + } +}; + +template +polygon(std::initializer_list

) -> polygon; + +template +struct polygon_outer : public point_container +{ + constexpr polygon_outer() noexcept = default; + constexpr polygon_outer(std::initializer_list

points) noexcept + : point_container(points) + { + } +}; + +template +polygon_outer(std::initializer_list

) -> polygon_outer

; + +template +struct polygon_inner : public point_container +{ + constexpr polygon_inner() noexcept = default; + constexpr polygon_inner(std::initializer_list

points) noexcept + : point_container(points) + { + } +}; + +template +polygon_inner(std::initializer_list

) -> polygon_inner

; + +template +struct polygons : public std::vector*> +{ + constexpr polygons() noexcept = default; + constexpr polygons(std::initializer_list*> polygons) noexcept + : std::vector*>(polygons) + { + } + + constexpr auto outer() noexcept + { + return polygon_outer{ this->front() }; + } + + constexpr auto inners() noexcept + { + return ranges::views::drop(this->base(), 1) + | ranges::views::transform( + [](auto& p) + { + return polygon_inner{ p }; + }); + } +}; + +template +polygons(polygon_outer

, std::initializer_list>) -> polygons

; + +} // namespace cura::gradual_flow::geometry + +static inline cura::gradual_flow::geometry::Point operator-(const cura::gradual_flow::geometry::Point& p0) +{ + return cura::gradual_flow::geometry::Point{ -p0.X, -p0.Y }; +} +static inline cura::gradual_flow::geometry::Point operator+(const cura::gradual_flow::geometry::Point& p0, const cura::gradual_flow::geometry::Point& p1) +{ + return cura::gradual_flow::geometry::Point{ p0.X + p1.X, p0.Y + p1.Y }; +} +static inline cura::gradual_flow::geometry::Point operator-(const cura::gradual_flow::geometry::Point& p0, const cura::gradual_flow::geometry::Point& p1) +{ + return cura::gradual_flow::geometry::Point{ p0.X - p1.X, p0.Y - p1.Y }; +} + +#endif // GRADUAL_FLOW_POINT_CONTAINER_H diff --git a/include/gradual_flow/processor.h b/include/gradual_flow/processor.h new file mode 100644 index 0000000000..9c984c03a9 --- /dev/null +++ b/include/gradual_flow/processor.h @@ -0,0 +1,100 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef GRADUAL_FLOW_PROCESSOR_H +#define GRADUAL_FLOW_PROCESSOR_H + +#include "Application.h" +#include "LayerPlan.h" +#include "Scene.h" +#include "gradual_flow/gcode_path.h" + +namespace cura::gradual_flow::Processor +{ +void process(std::vector& extruder_plan_paths, const size_t extruder_nr, const size_t layer_nr) +{ + const Scene& scene = Application::getInstance().current_slice_->scene; + const Settings& extruder_settings = scene.extruders[extruder_nr].settings_; + + if (extruder_settings.get("gradual_flow_enabled")) + { + // Convert the gcode paths to a format that suits our calculations more + std::vector gcode_paths; + + // Process first path + for (const cura::GCodePath& path : extruder_plan_paths | ranges::views::take(1)) + { + geometry::polyline<> points; + for (const Point2LL& point : path.points) + { + points.emplace_back(point); + } + gcode_paths.emplace_back(cura::gradual_flow::GCodePath{ .original_gcode_path_data = &path, .points = points }); + } + + /* Process remaining paths + * We need to add the last point of the previous path to the current path + * since the paths in Cura are a connected line string and a new path begins + * where the previous path ends (see figure below). + * { Path A } { Path B } { ...etc + * a.1-----------a.2------a.3---------a.4------b.1--------b.2--- c.1------- + * For our purposes it is easier that each path is a separate line string, and + * no knowledge of the previous path is needed. + */ + for (const auto& path : extruder_plan_paths | ranges::views::drop(1)) + { + geometry::polyline<> points{ ranges::back(ranges::back(gcode_paths).points) }; + for (const Point2LL& point : path.points) + { + points.emplace_back(point); + } + gcode_paths.emplace_back(cura::gradual_flow::GCodePath{ .original_gcode_path_data = &path, .points = points }); + } + + constexpr auto non_zero_flow_view = ranges::views::transform( + [](const auto& path) + { + return path.flow(); + }) + | ranges::views::drop_while( + [](const auto flow) + { + return flow == 0.0; + }); + auto gcode_paths_non_zero_flow_view = gcode_paths | non_zero_flow_view; + + const auto flow_limit = extruder_settings.get(layer_nr == 0 ? "layer_0_max_flow_acceleration" : "max_flow_acceleration"); + + auto target_flow = ranges::empty(gcode_paths_non_zero_flow_view) ? 0.0 : ranges::front(gcode_paths_non_zero_flow_view); + + GCodeState state{ + .current_flow = target_flow, + .flow_acceleration = flow_limit, + .flow_deceleration = flow_limit, + .discretized_duration = extruder_settings.get("gradual_flow_discretisation_step_size"), + // take the first path's target flow as the target flow, this might + // not be correct, but it is safe to assume the target flow for the + // next layer is the same as the target flow of the current layer + .target_end_flow = target_flow, + .reset_flow_duration = extruder_settings.get("reset_flow_duration"), + }; + + const auto limited_flow_acceleration_paths = state.processGcodePaths(gcode_paths); + // Copy newly generated paths to actual plan + + std::vector new_paths; + for (const auto& [index, gcode_path] : limited_flow_acceleration_paths | ranges::views::enumerate) + { + // since the first point is added from the previous path in the initial conversion, + // we should remove it here again. Note that the first point is added for every path + // except the first one, so we should only remove it if it is not the first path + const auto include_first_point = index == 0; + new_paths.push_back(gcode_path.toClassicPath(include_first_point)); + } + + extruder_plan_paths = new_paths; + } +} +} // namespace cura::gradual_flow::Processor + +#endif // GRADUAL_FLOW_PROCESSOR_H diff --git a/include/gradual_flow/utils.h b/include/gradual_flow/utils.h new file mode 100644 index 0000000000..1bcc21e60c --- /dev/null +++ b/include/gradual_flow/utils.h @@ -0,0 +1,85 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef GRADUAL_FLOW_UTILS_H +#define GRADUAL_FLOW_UTILS_H + + +namespace cura::gradual_flow::utils +{ + +enum Direction +{ + Forward, + Backward, +}; + +/* + * \brief Converts HSV values to RGB values. + * + * \param H Hue value in range [0, 360] + * \param S Saturation value in range [0, 100] + * \param V Value value in range [0, 100] + * + * \return A tuple containing the RGB values in range [0, 255] + */ +std::tuple hsvToRgb(double H, double S, double V) +{ + // Code taken from https://www.codespeedy.com/hsv-to-rgb-in-cpp/ and slightly modified + if (H > 360. || H < 0. || S > 100. || S < 0. || V > 100. || V < 0.) + { + throw std::invalid_argument("The given HSV values are not in valid range"); + } + auto s = S * .01; + auto v = V * .01; + auto C = s * v; + auto X = C * (1. - abs(fmod(H / 60.0, 2.) - 1.)); + auto m = v - C; + auto r = 0., g = 0., b = 0.; + if (H >= 0. && H < 60.) + { + r = C; + g = X; + b = 0.; + } + else if (H >= 60. && H < 120.) + { + r = X; + g = C; + b = 0.; + } + else if (H >= 120. && H < 180.) + { + r = 0.; + g = C; + b = X; + } + else if (H >= 180. && H < 240.) + { + r = 0.; + g = X; + b = C; + } + else if (H >= 240. && H < 300.) + { + r = X; + g = 0.; + b = C; + } + else + { + r = C; + g = 0.; + b = X; + } + + int R = (r + m) * 255.; + int G = (g + m) * 255.; + int B = (b + m) * 255.; + + return std::make_tuple(R, G, B); +} + +} // namespace cura::gradual_flow::utils + +#endif // GRADUAL_FLOW_UTILS_H diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 669bbfd5d8..552f1c9c60 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -1270,6 +1270,8 @@ FffGcodeWriter::ProcessLayerResult FffGcodeWriter::processLayer(const SliceDataS } } + gcode_layer.applyGradualFlow(); + gcode_layer.applyModifyPlugin(); time_keeper.registerTime("Modify plugin"); diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index ee0b331396..946d3653af 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -19,6 +19,7 @@ #include "WipeScriptConfig.h" #include "communication/Communication.h" #include "geometry/OpenPolyline.h" +#include "gradual_flow/processor.h" #include "pathPlanning/Comb.h" #include "pathPlanning/CombPaths.h" #include "plugins/slots.h" @@ -645,8 +646,6 @@ void LayerPlan::addPolygonsByOptimizer( static constexpr double max_non_bridge_line_volume = MM2INT(100); // limit to accumulated "volume" of non-bridge lines which is proportional to distance x extrusion rate -static int i = 0; - void LayerPlan::addWallLine( const Point2LL& p0, const Point2LL& p1, @@ -2632,6 +2631,14 @@ void LayerPlan::applyBackPressureCompensation() } } +void LayerPlan::applyGradualFlow() +{ + for (ExtruderPlan& extruder_plan : extruder_plans_) + { + gradual_flow::Processor::process(extruder_plan.paths_, extruder_plan.extruder_nr_, layer_nr_); + } +} + LayerIndex LayerPlan::getLayerNr() const { return layer_nr_; From 6083f4341c574f97333e74a6cb913aafb54c2e34 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 16 Aug 2024 14:39:57 +0200 Subject: [PATCH 2/6] Fix missing setting value conversion CURA-12096 --- include/gradual_flow/processor.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/gradual_flow/processor.h b/include/gradual_flow/processor.h index 9c984c03a9..520b1ac015 100644 --- a/include/gradual_flow/processor.h +++ b/include/gradual_flow/processor.h @@ -63,7 +63,7 @@ void process(std::vector& extruder_plan_paths, const size_t ext }); auto gcode_paths_non_zero_flow_view = gcode_paths | non_zero_flow_view; - const auto flow_limit = extruder_settings.get(layer_nr == 0 ? "layer_0_max_flow_acceleration" : "max_flow_acceleration"); + const auto flow_limit = extruder_settings.get(layer_nr == 0 ? "layer_0_max_flow_acceleration" : "max_flow_acceleration") * 1e9; auto target_flow = ranges::empty(gcode_paths_non_zero_flow_view) ? 0.0 : ranges::front(gcode_paths_non_zero_flow_view); From 38357c4d6a604b495ab26db68c917c98d0ff8c99 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 16 Aug 2024 15:59:25 +0200 Subject: [PATCH 3/6] Rename gradual flow classes and structs for consistency CURA-12096 Also removed the "boost_tags.h" header which seems useless in this context. And also renamed the GCodePath struct defined by gradual flow to FlowLimitedPath so that it is less confusing with the already defined GCodePath. --- .../gradual_flow/{concepts.h => Concepts.h} | 43 +++--- .../{gcode_path.h => FlowLimitedPath.h} | 45 +++--- include/gradual_flow/PointContainer.h | 120 ++++++++++++++++ .../gradual_flow/{processor.h => Processor.h} | 18 +-- include/gradual_flow/{utils.h => Utils.h} | 0 include/gradual_flow/boost_tags.h | 135 ------------------ include/gradual_flow/point_container.h | 134 ----------------- src/LayerPlan.cpp | 2 +- 8 files changed, 175 insertions(+), 322 deletions(-) rename include/gradual_flow/{concepts.h => Concepts.h} (60%) rename include/gradual_flow/{gcode_path.h => FlowLimitedPath.h} (91%) create mode 100644 include/gradual_flow/PointContainer.h rename include/gradual_flow/{processor.h => Processor.h} (85%) rename include/gradual_flow/{utils.h => Utils.h} (100%) delete mode 100644 include/gradual_flow/boost_tags.h delete mode 100644 include/gradual_flow/point_container.h diff --git a/include/gradual_flow/concepts.h b/include/gradual_flow/Concepts.h similarity index 60% rename from include/gradual_flow/concepts.h rename to include/gradual_flow/Concepts.h index 923157ada7..885922911b 100644 --- a/include/gradual_flow/concepts.h +++ b/include/gradual_flow/Concepts.h @@ -18,7 +18,8 @@ namespace cura::gradual_flow { -enum class direction + +enum class Direction { NA, CW, @@ -29,43 +30,43 @@ namespace concepts { template -concept closable = requires(T t) +concept Closable = requires(T t) { requires ranges::convertible_to; }; template -concept is_closed_point_container = closable && requires(T t) +concept IsClosedPointContainer = Closable && requires(T t) { t.is_closed == true; }; template -concept is_open_point_container = closable && requires(T t) +concept IsOpenPointContainer = Closable && requires(T t) { t.is_closed == false; }; template -concept directional = requires(T t) +concept Directional = requires(T t) { - requires std::is_same_v; + requires std::is_same_v; }; template -concept is_clockwise_point_container = directional && requires(T t) +concept IsClockwisePointContainer = Directional && requires(T t) { - t.winding == direction::CW; + t.winding == Direction::CW; }; template -concept is_counterclockwise_point_container = directional && requires(T t) +concept IsCounterclockwisePointContainer = Directional && requires(T t) { - t.winding == direction::CCW; + t.winding == Direction::CCW; }; template -concept point2d_named = requires(T point) +concept Point2DNamed = requires(T point) { point.X; point.Y; @@ -77,10 +78,10 @@ concept point2d_named = requires(T point) * @tparam T Type to check */ template -concept point2d = point2d_named ||(ranges::range&& ranges::integral&& std::tuple_size_v == 2); +concept Point2D = Point2DNamed ||(ranges::range&& ranges::integral&& std::tuple_size_v == 2); template -concept point3d_named = requires(T point) +concept Point3DNamed = requires(T point) { point.x; point.y; @@ -93,10 +94,10 @@ concept point3d_named = requires(T point) * @tparam T Type to check */ template -concept point3d = point3d_named ||(ranges::range&& ranges::integral&& std::tuple_size_v == 3); +concept Point3D = Point3DNamed ||(ranges::range&& ranges::integral&& std::tuple_size_v == 3); template -concept point_named = point2d_named || point3d_named; +concept PointNamed = Point2DNamed || Point3DNamed; /*! * @brief Either a Point2D or a Point3D @@ -104,22 +105,22 @@ concept point_named = point2d_named || point3d_named; * @tparam T Type to check */ template -concept point = point2d || point3d; +concept Point = Point2D || Point3D; template -concept point_ranged = point && ! point2d_named && ! point3d_named; +concept PointRanged = Point && ! Point2DNamed && ! Point3DNamed; template -concept polyline = ranges::range && is_open_point_container && point; +concept Polyline = ranges::range && IsOpenPointContainer && Point; template -concept polygon = ranges::range && is_closed_point_container && point; +concept Polygon = ranges::range && IsClosedPointContainer && Point; template -concept polygons = ranges::range && polygon; +concept Polygons = ranges::range && Polygon; template -concept poly_range = polygon || polyline; +concept PolyRange = Polygon || Polyline; } // namespace concepts } // namespace cura::gradual_flow diff --git a/include/gradual_flow/gcode_path.h b/include/gradual_flow/FlowLimitedPath.h similarity index 91% rename from include/gradual_flow/gcode_path.h rename to include/gradual_flow/FlowLimitedPath.h index 56c278dbf0..688ec7ef1b 100644 --- a/include/gradual_flow/gcode_path.h +++ b/include/gradual_flow/FlowLimitedPath.h @@ -13,8 +13,8 @@ #include #include -#include "gradual_flow/point_container.h" -#include "gradual_flow/utils.h" +#include "gradual_flow/PointContainer.h" +#include "gradual_flow/Utils.h" #include "pathPlanning/GCodePath.h" namespace cura::gradual_flow @@ -27,10 +27,10 @@ enum class FlowState UNDEFINED }; -struct GCodePath +struct FlowLimitedPath { - const cura::GCodePath* original_gcode_path_data; - geometry::polyline<> points; + const GCodePath* original_gcode_path_data; + geometry::Polyline<> points; double speed{ targetSpeed() }; // um/s double flow_{ extrusionVolumePerMm() * speed }; // um/s double total_length{ totalLength() }; // um @@ -165,13 +165,14 @@ struct GCodePath * partitioned path is equal or longer than the duration of the original * path */ - std::tuple, double> partition(const double partition_duration, const double partition_speed, const utils::Direction direction) const + std::tuple, double> + partition(const double partition_duration, const double partition_speed, const utils::Direction direction) const { const auto total_path_duration = total_length / partition_speed; if (partition_duration >= total_path_duration) { const auto remaining_partition_duration = partition_duration - total_path_duration; - const GCodePath gcode_path{ .original_gcode_path_data = original_gcode_path_data, .points = points, .speed = partition_speed }; + const FlowLimitedPath gcode_path{ .original_gcode_path_data = original_gcode_path_data, .points = points, .speed = partition_speed }; return std::make_tuple(gcode_path, std::nullopt, remaining_partition_duration); } @@ -226,7 +227,7 @@ struct GCodePath const auto partition_point_index = direction == utils::Direction::Forward ? partition_index + 1 : partition_index; // points left of the partition_index - geometry::polyline<> left_points; + geometry::Polyline<> left_points; for (unsigned int i = 0; i < partition_point_index; ++i) { left_points.emplace_back(points[i]); @@ -234,7 +235,7 @@ struct GCodePath left_points.emplace_back(partition_point); // points right of the partition_index - geometry::polyline<> right_points; + geometry::Polyline<> right_points; right_points.emplace_back(partition_point); for (unsigned int i = partition_point_index; i < points.size(); ++i) { @@ -245,12 +246,12 @@ struct GCodePath { case utils::Direction::Forward: { - const GCodePath partition_gcode_path{ + const FlowLimitedPath partition_gcode_path{ .original_gcode_path_data = original_gcode_path_data, .points = left_points, .speed = partition_speed, }; - const GCodePath remaining_gcode_path{ + const FlowLimitedPath remaining_gcode_path{ .original_gcode_path_data = original_gcode_path_data, .points = right_points, .speed = speed, @@ -259,12 +260,12 @@ struct GCodePath }; case utils::Direction::Backward: { - const GCodePath partition_gcode_path{ + const FlowLimitedPath partition_gcode_path{ .original_gcode_path_data = original_gcode_path_data, .points = right_points, .speed = partition_speed, }; - const GCodePath remaining_gcode_path{ + const FlowLimitedPath remaining_gcode_path{ .original_gcode_path_data = original_gcode_path_data, .points = left_points, .speed = speed, @@ -276,9 +277,9 @@ struct GCodePath } } - cura::GCodePath toClassicPath(const bool include_first_point) const + GCodePath toClassicPath(const bool include_first_point) const { - cura::GCodePath output_path = *original_gcode_path_data; + GCodePath output_path = *original_gcode_path_data; output_path.points.clear(); for (auto& point : points | ranges::views::drop(include_first_point ? 0 : 1)) @@ -303,12 +304,12 @@ struct GCodeState double reset_flow_duration{ 0.0 }; // s FlowState flow_state{ FlowState::UNDEFINED }; - std::vector processGcodePaths(const std::vector& gcode_paths) + std::vector processGcodePaths(const std::vector& gcode_paths) { // reset the discretized_duration_remaining discretized_duration_remaining = 0; - std::vector forward_pass_gcode_paths; + std::vector forward_pass_gcode_paths; for (auto& gcode_path : gcode_paths) { auto discretized_paths = processGcodePath(gcode_path, gradual_flow::utils::Direction::Forward); @@ -327,7 +328,7 @@ struct GCodeState // instead. current_flow = std::min(current_flow, target_end_flow); - std::list backward_pass_gcode_paths; + std::list backward_pass_gcode_paths; for (auto& gcode_path : forward_pass_gcode_paths | ranges::views::reverse) { auto discretized_paths = processGcodePath(gcode_path, gradual_flow::utils::Direction::Backward); @@ -337,7 +338,7 @@ struct GCodeState } } - return std::vector(backward_pass_gcode_paths.begin(), backward_pass_gcode_paths.end()); + return std::vector(backward_pass_gcode_paths.begin(), backward_pass_gcode_paths.end()); } /* @@ -347,7 +348,7 @@ struct GCodeState * * @return a vector of discretized paths with a gradual increase in flow */ - std::vector processGcodePath(const GCodePath& path, const utils::Direction direction) + std::vector processGcodePath(const FlowLimitedPath& path, const utils::Direction direction) { if (path.isTravel()) { @@ -375,9 +376,9 @@ struct GCodeState const auto extrusion_volume_per_mm = path.extrusionVolumePerMm(); // um^3/um - std::vector discretized_paths; + std::vector discretized_paths; - GCodePath remaining_path = path; + FlowLimitedPath remaining_path = path; if (discretized_duration_remaining > 0.) { diff --git a/include/gradual_flow/PointContainer.h b/include/gradual_flow/PointContainer.h new file mode 100644 index 0000000000..89602f3d3d --- /dev/null +++ b/include/gradual_flow/PointContainer.h @@ -0,0 +1,120 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef GRADUAL_FLOW_POINT_CONTAINER_H +#define GRADUAL_FLOW_POINT_CONTAINER_H + +#include +#include +#include +#include + +#include +#include + +#include "geometry/Point2LL.h" +#include "gradual_flow/Concepts.h" + +namespace cura::gradual_flow::geometry +{ + +/*! The base clase of all point based container types + * + * @tparam P + * @tparam IsClosed + * @tparam Direction + * @tparam Container + */ +template +struct PointContainer : public std::vector

+{ + inline static constexpr bool is_closed = IsClosed; + inline static constexpr Direction winding = direction; + + constexpr PointContainer() noexcept = default; + constexpr PointContainer(std::initializer_list

points) noexcept + : std::vector

(points) + { + } +}; + +template +struct Polyline : public PointContainer +{ + constexpr Polyline() noexcept = default; + constexpr Polyline(std::initializer_list

points) noexcept + : PointContainer(points) + { + } +}; + +template +struct Polygon : public PointContainer +{ + constexpr Polygon() noexcept = default; + constexpr Polygon(std::initializer_list

points) noexcept + : PointContainer(points) + { + } +}; + +template +Polygon(std::initializer_list

) -> Polygon; + +template +struct PolygonOuter : public PointContainer +{ + constexpr PolygonOuter() noexcept = default; + constexpr PolygonOuter(std::initializer_list

points) noexcept + : PointContainer(points) + { + } +}; + +template +PolygonOuter(std::initializer_list

) -> PolygonOuter

; + +template +struct PolygonInner : public PointContainer +{ + constexpr PolygonInner() noexcept = default; + constexpr PolygonInner(std::initializer_list

points) noexcept + : PointContainer(points) + { + } +}; + +template +PolygonInner(std::initializer_list

) -> PolygonInner

; + +template +struct Polygons : public std::vector*> +{ + constexpr Polygons() noexcept = default; + constexpr Polygons(std::initializer_list*> polygons) noexcept + : std::vector*>(polygons) + { + } + + constexpr auto outer() noexcept + { + return PolygonOuter{ this->front() }; + } + + constexpr auto inners() noexcept + { + return ranges::views::drop(this->base(), 1) + | ranges::views::transform( + [](auto& p) + { + return PolygonInner{ p }; + }); + } +}; + +template +Polygons(PolygonOuter

, std::initializer_list>) -> Polygons

; + +} // namespace cura::gradual_flow::geometry + +#endif // GRADUAL_FLOW_POINT_CONTAINER_H diff --git a/include/gradual_flow/processor.h b/include/gradual_flow/Processor.h similarity index 85% rename from include/gradual_flow/processor.h rename to include/gradual_flow/Processor.h index 520b1ac015..c73465a54d 100644 --- a/include/gradual_flow/processor.h +++ b/include/gradual_flow/Processor.h @@ -7,11 +7,11 @@ #include "Application.h" #include "LayerPlan.h" #include "Scene.h" -#include "gradual_flow/gcode_path.h" +#include "gradual_flow/FlowLimitedPath.h" namespace cura::gradual_flow::Processor { -void process(std::vector& extruder_plan_paths, const size_t extruder_nr, const size_t layer_nr) +void process(std::vector& extruder_plan_paths, const size_t extruder_nr, const size_t layer_nr) { const Scene& scene = Application::getInstance().current_slice_->scene; const Settings& extruder_settings = scene.extruders[extruder_nr].settings_; @@ -19,17 +19,17 @@ void process(std::vector& extruder_plan_paths, const size_t ext if (extruder_settings.get("gradual_flow_enabled")) { // Convert the gcode paths to a format that suits our calculations more - std::vector gcode_paths; + std::vector gcode_paths; // Process first path - for (const cura::GCodePath& path : extruder_plan_paths | ranges::views::take(1)) + for (const GCodePath& path : extruder_plan_paths | ranges::views::take(1)) { - geometry::polyline<> points; + geometry::Polyline<> points; for (const Point2LL& point : path.points) { points.emplace_back(point); } - gcode_paths.emplace_back(cura::gradual_flow::GCodePath{ .original_gcode_path_data = &path, .points = points }); + gcode_paths.emplace_back(FlowLimitedPath{ .original_gcode_path_data = &path, .points = points }); } /* Process remaining paths @@ -43,12 +43,12 @@ void process(std::vector& extruder_plan_paths, const size_t ext */ for (const auto& path : extruder_plan_paths | ranges::views::drop(1)) { - geometry::polyline<> points{ ranges::back(ranges::back(gcode_paths).points) }; + geometry::Polyline<> points{ ranges::back(ranges::back(gcode_paths).points) }; for (const Point2LL& point : path.points) { points.emplace_back(point); } - gcode_paths.emplace_back(cura::gradual_flow::GCodePath{ .original_gcode_path_data = &path, .points = points }); + gcode_paths.emplace_back(FlowLimitedPath{ .original_gcode_path_data = &path, .points = points }); } constexpr auto non_zero_flow_view = ranges::views::transform( @@ -82,7 +82,7 @@ void process(std::vector& extruder_plan_paths, const size_t ext const auto limited_flow_acceleration_paths = state.processGcodePaths(gcode_paths); // Copy newly generated paths to actual plan - std::vector new_paths; + std::vector new_paths; for (const auto& [index, gcode_path] : limited_flow_acceleration_paths | ranges::views::enumerate) { // since the first point is added from the previous path in the initial conversion, diff --git a/include/gradual_flow/utils.h b/include/gradual_flow/Utils.h similarity index 100% rename from include/gradual_flow/utils.h rename to include/gradual_flow/Utils.h diff --git a/include/gradual_flow/boost_tags.h b/include/gradual_flow/boost_tags.h deleted file mode 100644 index 9e2273acd5..0000000000 --- a/include/gradual_flow/boost_tags.h +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) 2024 UltiMaker -// CuraEngine is released under the terms of the AGPLv3 or higher - -#ifndef GRADUAL_FLOW_BOOST_TAGS_H -#define GRADUAL_FLOW_BOOST_TAGS_H - - -#ifndef INFILL_BOOST_TAGS_H -#define INFILL_BOOST_TAGS_H - -#include -#include - -#include -#include -#include -#include -#include - -#include "gradual_flow/concepts.h" -#include "gradual_flow/point_container.h" - -namespace boost::geometry::traits -{ - -template<> -struct tag -{ - using type = point_tag; -}; - -template<> -struct dimension : boost::mpl::int_<2> -{ -}; - -template<> -struct coordinate_type -{ - using type = ClipperLib::cInt; -}; - -template<> -struct coordinate_system -{ - using type = boost::geometry::cs::cartesian; -}; - -template -struct access -{ - static_assert(Index < 2, "Out of range"); - using Point = ClipperLib::IntPoint; - using CoordinateType = typename coordinate_type::type; - constexpr static inline CoordinateType get(Point const& p) noexcept - { - return Index == 0 ? p.X : p.Y; - } - - constexpr static inline void set(Point& p, CoordinateType const& value) noexcept - { - if (Index == 0) - { - p.X = value; - } - else - { - p.Y = value; - } - } -}; - -template<> -struct tag> -{ - using type = linestring_tag; -}; - -template<> -struct point_order> -{ - static const order_selector value = clockwise; -}; - -template<> -struct closure> -{ - static const closure_selector value = open; -}; - -template<> -struct tag> -{ - using type = ring_tag; -}; - -template<> -struct point_order> -{ - static const order_selector value = counterclockwise; -}; - -template<> -struct closure> -{ - static const closure_selector value = open; -}; - -template<> -struct tag> -{ - using type = ring_tag; -}; - -template<> -struct point_order -{ - static const order_selector value = counterclockwise; -}; - -template<> -struct closure -{ - static const closure_selector value = open; -}; - -template<> -struct tag -{ - using type = ring_tag; -}; - -} // namespace boost::geometry::traits - -#endif // GRADUAL_FLOW_BOOST_TAGS_H diff --git a/include/gradual_flow/point_container.h b/include/gradual_flow/point_container.h deleted file mode 100644 index ae070eeddc..0000000000 --- a/include/gradual_flow/point_container.h +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright (c) 2024 UltiMaker -// CuraEngine is released under the terms of the AGPLv3 or higher - -#ifndef GRADUAL_FLOW_POINT_CONTAINER_H -#define GRADUAL_FLOW_POINT_CONTAINER_H - -#include -#include -#include -#include - -#include -#include - -#include "gradual_flow/concepts.h" - -namespace cura::gradual_flow::geometry -{ - -using Point = ClipperLib::IntPoint; - -/*! The base clase of all point based container types - * - * @tparam P - * @tparam IsClosed - * @tparam Direction - * @tparam Container - */ -template -struct point_container : public std::vector

-{ - inline static constexpr bool is_closed = IsClosed; - inline static constexpr direction winding = Direction; - - constexpr point_container() noexcept = default; - constexpr point_container(std::initializer_list

points) noexcept - : std::vector

(points) - { - } -}; - -template -struct polyline : public point_container -{ - constexpr polyline() noexcept = default; - constexpr polyline(std::initializer_list

points) noexcept - : point_container(points) - { - } -}; - -template -struct polygon : public point_container -{ - constexpr polygon() noexcept = default; - constexpr polygon(std::initializer_list

points) noexcept - : point_container(points) - { - } -}; - -template -polygon(std::initializer_list

) -> polygon; - -template -struct polygon_outer : public point_container -{ - constexpr polygon_outer() noexcept = default; - constexpr polygon_outer(std::initializer_list

points) noexcept - : point_container(points) - { - } -}; - -template -polygon_outer(std::initializer_list

) -> polygon_outer

; - -template -struct polygon_inner : public point_container -{ - constexpr polygon_inner() noexcept = default; - constexpr polygon_inner(std::initializer_list

points) noexcept - : point_container(points) - { - } -}; - -template -polygon_inner(std::initializer_list

) -> polygon_inner

; - -template -struct polygons : public std::vector*> -{ - constexpr polygons() noexcept = default; - constexpr polygons(std::initializer_list*> polygons) noexcept - : std::vector*>(polygons) - { - } - - constexpr auto outer() noexcept - { - return polygon_outer{ this->front() }; - } - - constexpr auto inners() noexcept - { - return ranges::views::drop(this->base(), 1) - | ranges::views::transform( - [](auto& p) - { - return polygon_inner{ p }; - }); - } -}; - -template -polygons(polygon_outer

, std::initializer_list>) -> polygons

; - -} // namespace cura::gradual_flow::geometry - -static inline cura::gradual_flow::geometry::Point operator-(const cura::gradual_flow::geometry::Point& p0) -{ - return cura::gradual_flow::geometry::Point{ -p0.X, -p0.Y }; -} -static inline cura::gradual_flow::geometry::Point operator+(const cura::gradual_flow::geometry::Point& p0, const cura::gradual_flow::geometry::Point& p1) -{ - return cura::gradual_flow::geometry::Point{ p0.X + p1.X, p0.Y + p1.Y }; -} -static inline cura::gradual_flow::geometry::Point operator-(const cura::gradual_flow::geometry::Point& p0, const cura::gradual_flow::geometry::Point& p1) -{ - return cura::gradual_flow::geometry::Point{ p0.X - p1.X, p0.Y - p1.Y }; -} - -#endif // GRADUAL_FLOW_POINT_CONTAINER_H diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 946d3653af..ce654d76ec 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -19,7 +19,7 @@ #include "WipeScriptConfig.h" #include "communication/Communication.h" #include "geometry/OpenPolyline.h" -#include "gradual_flow/processor.h" +#include "gradual_flow/Processor.h" #include "pathPlanning/Comb.h" #include "pathPlanning/CombPaths.h" #include "plugins/slots.h" From 4eb7b1e74e149dd41023183370a4f0421b643ca9 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 16 Aug 2024 16:14:42 +0200 Subject: [PATCH 4/6] Add documentation on main methods CURA-12096 --- include/LayerPlan.h | 4 ++++ include/gradual_flow/Processor.h | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/include/LayerPlan.h b/include/LayerPlan.h index 7833e0c8e9..952d7b4921 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -778,6 +778,10 @@ class LayerPlan : public NoCopy */ void applyBackPressureCompensation(); + /*! + * If enabled, applies the gradual flow acceleration splitting, that improves printing quality when printing at very high speed + * with a bowden extruder. + */ void applyGradualFlow(); private: diff --git a/include/gradual_flow/Processor.h b/include/gradual_flow/Processor.h index c73465a54d..1a5ad9cc19 100644 --- a/include/gradual_flow/Processor.h +++ b/include/gradual_flow/Processor.h @@ -11,6 +11,14 @@ namespace cura::gradual_flow::Processor { + +/*! + * \brief Processes the gradual flow acceleration splitting + * \param extruder_plan_paths The paths of the extruder plan to be processed. I gradual flow is enabled, they will be + * completely rewritten, very likely with a different amout of output paths. + * \param extruder_nr The used extruder number + * \param layer_nr The current layer number + */ void process(std::vector& extruder_plan_paths, const size_t extruder_nr, const size_t layer_nr) { const Scene& scene = Application::getInstance().current_slice_->scene; From f0c0d199edcd575da069b755e40b1c1b9d1f2b84 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 16 Aug 2024 16:41:58 +0200 Subject: [PATCH 5/6] Refine comment CURA-12096 --- include/LayerPlan.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/LayerPlan.h b/include/LayerPlan.h index 952d7b4921..0e26b35e66 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -779,8 +779,8 @@ class LayerPlan : public NoCopy void applyBackPressureCompensation(); /*! - * If enabled, applies the gradual flow acceleration splitting, that improves printing quality when printing at very high speed - * with a bowden extruder. + * If enabled, applies the gradual flow acceleration splitting, that improves printing quality when printing at very high speed, + * especially with a bowden extruder. */ void applyGradualFlow(); From ef4426e8612f456ce84dc1958b02ac6cc7e4af51 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 16 Aug 2024 17:02:15 +0200 Subject: [PATCH 6/6] Use native geometry classes CURA-12096 --- include/geometry/LinesSet.h | 2 +- include/geometry/PointsSet.h | 6 ++ include/gradual_flow/Concepts.h | 128 ------------------------- include/gradual_flow/FlowLimitedPath.h | 8 +- include/gradual_flow/PointContainer.h | 120 ----------------------- include/gradual_flow/Processor.h | 14 +-- 6 files changed, 14 insertions(+), 264 deletions(-) delete mode 100644 include/gradual_flow/Concepts.h delete mode 100644 include/gradual_flow/PointContainer.h diff --git a/include/geometry/LinesSet.h b/include/geometry/LinesSet.h index 7834b821eb..11e5318dcc 100644 --- a/include/geometry/LinesSet.h +++ b/include/geometry/LinesSet.h @@ -158,7 +158,7 @@ class LinesSet template void push_back(LinesSet&& lines_set); - /*! \brief Pushes an entier set at the end */ + /*! \brief Pushes an entire set at the end */ void push_back(const LinesSet& other) { lines_.insert(lines_.end(), other.lines_.begin(), other.lines_.end()); diff --git a/include/geometry/PointsSet.h b/include/geometry/PointsSet.h index f61329929d..7292de13b1 100644 --- a/include/geometry/PointsSet.h +++ b/include/geometry/PointsSet.h @@ -78,6 +78,12 @@ class PointsSet points_.push_back(point); } + /*! \brief Pushes an entire set at the end */ + void push_back(const PointsSet& other) + { + points_.insert(points_.end(), other.points_.begin(), other.points_.end()); + } + void emplace_back(auto&&... args) { points_.emplace_back(std::forward(args)...); diff --git a/include/gradual_flow/Concepts.h b/include/gradual_flow/Concepts.h deleted file mode 100644 index 885922911b..0000000000 --- a/include/gradual_flow/Concepts.h +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) 2024 UltiMaker -// CuraEngine is released under the terms of the AGPLv3 or higher - -#ifndef GRADUAL_FLOW_CONCEPTS_H -#define GRADUAL_FLOW_CONCEPTS_H - -#include - -#if __has_include() -#include -#elif __has_include() -#include -#define USE_EXPERIMENTAL_CONCEPTS -#endif - -#include -#include - -namespace cura::gradual_flow -{ - -enum class Direction -{ - NA, - CW, - CCW -}; - -namespace concepts -{ - -template -concept Closable = requires(T t) -{ - requires ranges::convertible_to; -}; - -template -concept IsClosedPointContainer = Closable && requires(T t) -{ - t.is_closed == true; -}; - -template -concept IsOpenPointContainer = Closable && requires(T t) -{ - t.is_closed == false; -}; - -template -concept Directional = requires(T t) -{ - requires std::is_same_v; -}; - -template -concept IsClockwisePointContainer = Directional && requires(T t) -{ - t.winding == Direction::CW; -}; - -template -concept IsCounterclockwisePointContainer = Directional && requires(T t) -{ - t.winding == Direction::CCW; -}; - -template -concept Point2DNamed = requires(T point) -{ - point.X; - point.Y; -}; - -/*! - * @brief A 2D point, defined either as a named object with X and Y attributes, or as a range of two integral values. - * @details This concept is used to check if a type is a 2D point. A 2D point is a type that has a X and Y member or a type that is a range of integral types with a size of 2. - * @tparam T Type to check - */ -template -concept Point2D = Point2DNamed ||(ranges::range&& ranges::integral&& std::tuple_size_v == 2); - -template -concept Point3DNamed = requires(T point) -{ - point.x; - point.y; - point.z; -}; - -/*! - * @brief A 3D point, defined either as a named object with x, y, and z attributes, or as a range of three integral values. - * @details This concept is used to check if a type is a 3D point. A 3D point is a type that has a x, y and z member or a type that is a range of integral types with a size of 3. - * @tparam T Type to check - */ -template -concept Point3D = Point3DNamed ||(ranges::range&& ranges::integral&& std::tuple_size_v == 3); - -template -concept PointNamed = Point2DNamed || Point3DNamed; - -/*! - * @brief Either a Point2D or a Point3D - * @details This concept is used to check if a type is a point. A point is a type that is a 2D or 3D point. - * @tparam T Type to check - */ -template -concept Point = Point2D || Point3D; - -template -concept PointRanged = Point && ! Point2DNamed && ! Point3DNamed; - -template -concept Polyline = ranges::range && IsOpenPointContainer && Point; - -template -concept Polygon = ranges::range && IsClosedPointContainer && Point; - -template -concept Polygons = ranges::range && Polygon; - -template -concept PolyRange = Polygon || Polyline; - -} // namespace concepts -} // namespace cura::gradual_flow - -#endif // GRADUAL_FLOW_CONCEPTS_H diff --git a/include/gradual_flow/FlowLimitedPath.h b/include/gradual_flow/FlowLimitedPath.h index 688ec7ef1b..0e3eab859d 100644 --- a/include/gradual_flow/FlowLimitedPath.h +++ b/include/gradual_flow/FlowLimitedPath.h @@ -13,7 +13,7 @@ #include #include -#include "gradual_flow/PointContainer.h" +#include "geometry/PointsSet.h" #include "gradual_flow/Utils.h" #include "pathPlanning/GCodePath.h" @@ -30,7 +30,7 @@ enum class FlowState struct FlowLimitedPath { const GCodePath* original_gcode_path_data; - geometry::Polyline<> points; + PointsSet points; double speed{ targetSpeed() }; // um/s double flow_{ extrusionVolumePerMm() * speed }; // um/s double total_length{ totalLength() }; // um @@ -227,7 +227,7 @@ struct FlowLimitedPath const auto partition_point_index = direction == utils::Direction::Forward ? partition_index + 1 : partition_index; // points left of the partition_index - geometry::Polyline<> left_points; + PointsSet left_points; for (unsigned int i = 0; i < partition_point_index; ++i) { left_points.emplace_back(points[i]); @@ -235,7 +235,7 @@ struct FlowLimitedPath left_points.emplace_back(partition_point); // points right of the partition_index - geometry::Polyline<> right_points; + PointsSet right_points; right_points.emplace_back(partition_point); for (unsigned int i = partition_point_index; i < points.size(); ++i) { diff --git a/include/gradual_flow/PointContainer.h b/include/gradual_flow/PointContainer.h deleted file mode 100644 index 89602f3d3d..0000000000 --- a/include/gradual_flow/PointContainer.h +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright (c) 2024 UltiMaker -// CuraEngine is released under the terms of the AGPLv3 or higher - -#ifndef GRADUAL_FLOW_POINT_CONTAINER_H -#define GRADUAL_FLOW_POINT_CONTAINER_H - -#include -#include -#include -#include - -#include -#include - -#include "geometry/Point2LL.h" -#include "gradual_flow/Concepts.h" - -namespace cura::gradual_flow::geometry -{ - -/*! The base clase of all point based container types - * - * @tparam P - * @tparam IsClosed - * @tparam Direction - * @tparam Container - */ -template -struct PointContainer : public std::vector

-{ - inline static constexpr bool is_closed = IsClosed; - inline static constexpr Direction winding = direction; - - constexpr PointContainer() noexcept = default; - constexpr PointContainer(std::initializer_list

points) noexcept - : std::vector

(points) - { - } -}; - -template -struct Polyline : public PointContainer -{ - constexpr Polyline() noexcept = default; - constexpr Polyline(std::initializer_list

points) noexcept - : PointContainer(points) - { - } -}; - -template -struct Polygon : public PointContainer -{ - constexpr Polygon() noexcept = default; - constexpr Polygon(std::initializer_list

points) noexcept - : PointContainer(points) - { - } -}; - -template -Polygon(std::initializer_list

) -> Polygon; - -template -struct PolygonOuter : public PointContainer -{ - constexpr PolygonOuter() noexcept = default; - constexpr PolygonOuter(std::initializer_list

points) noexcept - : PointContainer(points) - { - } -}; - -template -PolygonOuter(std::initializer_list

) -> PolygonOuter

; - -template -struct PolygonInner : public PointContainer -{ - constexpr PolygonInner() noexcept = default; - constexpr PolygonInner(std::initializer_list

points) noexcept - : PointContainer(points) - { - } -}; - -template -PolygonInner(std::initializer_list

) -> PolygonInner

; - -template -struct Polygons : public std::vector*> -{ - constexpr Polygons() noexcept = default; - constexpr Polygons(std::initializer_list*> polygons) noexcept - : std::vector*>(polygons) - { - } - - constexpr auto outer() noexcept - { - return PolygonOuter{ this->front() }; - } - - constexpr auto inners() noexcept - { - return ranges::views::drop(this->base(), 1) - | ranges::views::transform( - [](auto& p) - { - return PolygonInner{ p }; - }); - } -}; - -template -Polygons(PolygonOuter

, std::initializer_list>) -> Polygons

; - -} // namespace cura::gradual_flow::geometry - -#endif // GRADUAL_FLOW_POINT_CONTAINER_H diff --git a/include/gradual_flow/Processor.h b/include/gradual_flow/Processor.h index 1a5ad9cc19..c9615d6ba2 100644 --- a/include/gradual_flow/Processor.h +++ b/include/gradual_flow/Processor.h @@ -32,12 +32,7 @@ void process(std::vector& extruder_plan_paths, const size_t extruder_ // Process first path for (const GCodePath& path : extruder_plan_paths | ranges::views::take(1)) { - geometry::Polyline<> points; - for (const Point2LL& point : path.points) - { - points.emplace_back(point); - } - gcode_paths.emplace_back(FlowLimitedPath{ .original_gcode_path_data = &path, .points = points }); + gcode_paths.emplace_back(FlowLimitedPath{ .original_gcode_path_data = &path, .points = PointsSet(path.points) }); } /* Process remaining paths @@ -51,11 +46,8 @@ void process(std::vector& extruder_plan_paths, const size_t extruder_ */ for (const auto& path : extruder_plan_paths | ranges::views::drop(1)) { - geometry::Polyline<> points{ ranges::back(ranges::back(gcode_paths).points) }; - for (const Point2LL& point : path.points) - { - points.emplace_back(point); - } + PointsSet points{ gcode_paths.back().points.back() }; + points.push_back(PointsSet(path.points)); gcode_paths.emplace_back(FlowLimitedPath{ .original_gcode_path_data = &path, .points = points }); }