Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CURA 10724 smooth operator #1891

Merged
merged 52 commits into from
Jul 4, 2023
Merged
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
0e93b77
Smooth operator
jellespijker Jun 24, 2023
9789cf0
reverted premature optimization
jellespijker Jun 24, 2023
e17eb83
Don't use costly angle calculations
jellespijker Jun 24, 2023
4097e93
Moved concepts to types and util namespace
jellespijker Jun 25, 2023
42093f7
Add some base geometry concepts
jellespijker Jun 25, 2023
46d414b
Allow uniform coordinate query with std::get
jellespijker Jun 25, 2023
f834149
Use std::get to obtain X,Y
jellespijker Jun 25, 2023
7f2f1cf
Allow smoothing of Arachne lines
jellespijker Jun 26, 2023
726fd09
Fixed failing UT
jellespijker Jun 26, 2023
a455e3a
Shift the points with the correct distance
jellespijker Jun 26, 2023
fc19696
Merge branch '5.4' into CURA-10724_smooth_operator
jellespijker Jun 27, 2023
34858fa
Refactor smooth action and fix pointer issues
jellespijker Jun 27, 2023
24a795a
Refactor smooth action in utils codebase
jellespijker Jun 28, 2023
5b86ae8
Optimize smoothing function and fix error in shift_points
jellespijker Jun 29, 2023
5c36cce
Refactor SmoothTest for more reliable results
jellespijker Jun 29, 2023
4a15a2c
Extended geometrical type concepts
jellespijker Jul 1, 2023
db25559
Add functionality to access 2D and 3D point coordinates by index or c…
jellespijker Jul 1, 2023
6027c54
Update comment syntax in geometry.h
jellespijker Jul 1, 2023
4e1c55f
Refactor smoothing function, remove unused parameter and improve read…
jellespijker Jul 1, 2023
41e260a
Refactor smoother functionality in WallToolPaths
jellespijker Jul 1, 2023
099a636
Refine path smoothing algorithm
jellespijker Jul 1, 2023
f4756d0
Merge branch '5.4' into CURA-10724_smooth_operator
jellespijker Jul 1, 2023
5b6bdc7
Update smoothing parameters in test
jellespijker Jul 1, 2023
f8544c1
Add missing import
jellespijker Jul 1, 2023
925c8ff
fix ranged 2d and 3d point concepts on Apple Clang 13
jellespijker Jul 1, 2023
eda05c1
Update Geometry checks for compiler support of std::integral
jellespijker Jul 1, 2023
5cd147b
Add note with stack-overflow comment
jellespijker Jul 1, 2023
414f9c6
Adjust Arachne type checks for different C++ versions
jellespijker Jul 1, 2023
973c653
Refactor std::integral usage for C++ compatibility
jellespijker Jul 1, 2023
1694fdc
Fix floating_point concept for Apple Clang <= 13
jellespijker Jul 1, 2023
ab6d68c
Improved detection of Apple Clang <= 13
jellespijker Jul 1, 2023
f9a3538
Refactor angle checking in smoothing utility
jellespijker Jul 2, 2023
f694ca4
Start the window at the tail
jellespijker Jul 2, 2023
9cc8e97
add missing include
jellespijker Jul 2, 2023
1edab47
Update test
jellespijker Jul 2, 2023
bc2a8c2
Replace 'tail' with 'single' in smoothing process
jellespijker Jul 2, 2023
6220a38
Less aggressive smoothing
jellespijker Jul 2, 2023
8173e59
Add logging for prepared outline in WallToolPaths
jellespijker Jul 2, 2023
0ad51a4
Reorder includes and add logging in SkeletalTrapezoidation
jellespijker Jul 2, 2023
ded69d4
Skip smoothing for support walls
jellespijker Jul 2, 2023
40a8fab
Use ranges concepts instead of std
jellespijker Jul 3, 2023
95234f7
Check against apple_clang instead of apple-clang
jellespijker Jul 3, 2023
140da4e
Revert "Check against apple_clang instead of apple-clang"
jellespijker Jul 3, 2023
b7ef734
Specify the exact types for the concept for Apple-Clang
jellespijker Jul 3, 2023
4bf339e
Moved concepts floating_point integral to std
jellespijker Jul 3, 2023
9fee071
Applied code-review suggestions
jellespijker Jul 3, 2023
3cc85bc
Check against version 14 of Apple-Clang
jellespijker Jul 3, 2023
49ea674
Improve code readability and add assertions
jellespijker Jul 3, 2023
a7caa49
Quick mock test for green marks
jellespijker Jul 3, 2023
be715bc
Switched the order of the smoothing
jellespijker Jul 3, 2023
d7fd718
Add unit tests
casperlamboo Jul 3, 2023
5044f34
Revert "Add unit tests"
casperlamboo Jul 4, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ option(ENABLE_TESTING "Build with unit tests" OFF)
option(EXTENSIVE_WARNINGS "Build with all warnings" ON)
option(ENABLE_MORE_COMPILER_OPTIMIZATION_FLAGS "Enable more optimization flags" ON)
option(USE_SYSTEM_LIBS "Use the system libraries if available" OFF)
option(RETARDED_APPLE_CLANG "Apple Clang <= 13 used" OFF)
jellespijker marked this conversation as resolved.
Show resolved Hide resolved

# Create Protobuf files if Arcus is used
if (ENABLE_ARCUS)
Expand Down Expand Up @@ -149,6 +150,7 @@ target_include_directories(_CuraEngine
target_compile_definitions(_CuraEngine
PUBLIC
$<$<BOOL:${ENABLE_ARCUS}>:ARCUS>
$<$<BOOL:${RETARDED_APPLE_CLANG}>:RETARDED_APPLE_CLANG>
CURA_ENGINE_VERSION=\"${CURA_ENGINE_VERSION}\"
$<$<BOOL:${ENABLE_TESTING}>:BUILD_TESTS>
PRIVATE
Expand Down
1 change: 1 addition & 0 deletions conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ def generate(self):
tc.variables["ENABLE_TESTING"] = self.options.enable_testing
tc.variables["ENABLE_BENCHMARKS"] = self.options.enable_benchmarks
tc.variables["EXTENSIVE_WARNINGS"] = self.options.enable_extensive_warnings
tc.variables["RETARDED_APPLE_CLANG"] = self.settings.compiler == "apple-clang" and Version(self.settings.compiler.version) <= Version("12")
tc.generate()

def layout(self):
Expand Down
151 changes: 151 additions & 0 deletions include/utils/actions/smooth.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// Copyright (c) 2023 UltiMaker
// CuraEngine is released under the terms of the AGPLv3 or higher

#ifndef UTILS_VIEWS_SMOOTH_H
#define UTILS_VIEWS_SMOOTH_H

#include <functional>
#include <numbers>
#include <set>

#include <range/v3/action/remove_if.hpp>
#include <range/v3/functional/bind_back.hpp>
#include <range/v3/iterator/concepts.hpp>
#include <range/v3/iterator/operations.hpp>
#include <range/v3/range_fwd.hpp>
#include <range/v3/view/addressof.hpp>
#include <range/v3/view/concat.hpp>
#include <range/v3/view/single.hpp>
#include <range/v3/view/take.hpp>

#include "utils/types/arachne.h"
#include "utils/types/generic.h"
#include "utils/types/geometry.h"
#include "utils/types/get.h"

namespace cura::actions
{

struct smooth_fn
{
constexpr auto operator()(const std::integral auto max_resolution, const std::floating_point auto fluid_angle) const
{
return ranges::make_action_closure(ranges::bind_back(smooth_fn{}, max_resolution, fluid_angle));
}

template<class Rng>
requires ranges::forward_range<Rng>&& ranges::sized_range<Rng>&& ranges::erasable_range<Rng, ranges::iterator_t<Rng>, ranges::sentinel_t<Rng>> && (utils::point2d<ranges::range_value_t<Rng>> || utils::junctions<Rng>)
jellespijker marked this conversation as resolved.
Show resolved Hide resolved
constexpr auto operator()(Rng&& rng, const std::integral auto max_resolution, const std::floating_point auto fluid_angle) const
{
const auto size = ranges::distance(rng) - 1; // TODO: implement for open paths! The value `-1` is for closed Paths, if open then subtract `0`
jellespijker marked this conversation as resolved.
Show resolved Hide resolved
if (size < 3)
{
return static_cast<Rng&&>(rng);
}

using coord_type = std::remove_cvref_t<decltype(std::get<"X">(*ranges::begin(rng)))>;
const auto allowed_deviation = static_cast<coord_type>(max_resolution * 2 / 3); // The allowed deviation from the original path
const auto smooth_distance = static_cast<coord_type>(max_resolution / 2); // The distance over which the path is smoothed

auto tmp = rng; // We don't want to shift the points of the ingoing range, therefor we create a temporary copy
jellespijker marked this conversation as resolved.
Show resolved Hide resolved
auto windows = ranges::views::concat(ranges::views::single(ranges::back(tmp)), ranges::views::concat(tmp, tmp | ranges::views::take(4))) | ranges::views::addressof;

// Smooth the path, by moving over three segments at a time. If the middle segment is shorter than the max resolution, then we try shifting those points outwards.
nallath marked this conversation as resolved.
Show resolved Hide resolved
// The previous and next segment should have a remaining length of at least the smooth distance, otherwise the point is not shifted, but deleted.
for (auto windows_it = ranges::begin(windows); ranges::distance(windows_it, ranges::end(windows)) > 2; ++windows_it)
{
auto A = *windows_it;
auto B = *std::next(windows_it, 1);
auto C = *std::next(windows_it, 2);
auto D = *std::next(windows_it, 3);

const auto [AB_magnitude, BC_magnitude, CD_magnitude] = computeMagnitudes(A, B, C, D);
if (! isWithinAllowedDeviations(A, B, C, D, fluid_angle, max_resolution, AB_magnitude, BC_magnitude, CD_magnitude))
{
if (AB_magnitude > allowed_deviation)
{
shiftPointTowards(B, A, AB_magnitude, smooth_distance);
}
if (CD_magnitude > allowed_deviation)
{
shiftPointTowards(C, D, CD_magnitude, smooth_distance);
}
}
}

return tmp;
}

private:
template<class Point>
requires utils::point2d<Point> || utils::junction<Point>
constexpr auto computeMagnitudes(Point* A, Point* B, Point* C, Point* D) const noexcept
{
const auto AB_magnitude = std::hypot(std::get<"X">(*B) - std::get<"X">(*A), std::get<"Y">(*B) - std::get<"Y">(*A));
const auto BC_magnitude = std::hypot(std::get<"X">(*C) - std::get<"X">(*B), std::get<"Y">(*C) - std::get<"Y">(*B));
const auto CD_magnitude = std::hypot(std::get<"X">(*D) - std::get<"X">(*C), std::get<"Y">(*D) - std::get<"Y">(*C));

return std::make_tuple(AB_magnitude, BC_magnitude, CD_magnitude);
}

template<class Point>
requires utils::point2d<Point> || utils::junction<Point>
constexpr auto cosAngle(Point* A, Point* B, Point* C, const std::floating_point auto AB_magnitude, const std::floating_point auto BC_magnitude) const noexcept
{
if (AB_magnitude == 0.0 || BC_magnitude == 0.0)
{
return 0.0;
}
auto AB = std::make_tuple(std::get<"X">(*B) - std::get<"X">(*A), std::get<"Y">(*B) - std::get<"Y">(*A));
auto BC = std::make_tuple(std::get<"X">(*C) - std::get<"X">(*B), std::get<"Y">(*C) - std::get<"Y">(*B));

const auto dot = dotProduct(&AB, &BC);
return dot / (AB_magnitude * BC_magnitude);
}

template<class Point>
requires utils::point2d<Point> || utils::junction<Point>
constexpr void shiftPointTowards(Point* point, Point* target, const std::floating_point auto p0p1_distance, const std::integral auto smooth_distance) const noexcept
{
using coord_type = std::remove_cvref_t<decltype(std::get<"X">(*point))>;
const auto shift_distance = smooth_distance / p0p1_distance;
const auto shift_distance_x = static_cast<coord_type>((std::get<"X">(*target) - std::get<"X">(*point)) * shift_distance);
const auto shift_distance_y = static_cast<coord_type>((std::get<"Y">(*target) - std::get<"Y">(*point)) * shift_distance);
if constexpr (utils::point2d<Point>)
{
point->X += shift_distance_x;
point->Y += shift_distance_y;
}
else
{
point->p.X += shift_distance_x;
point->p.Y += shift_distance_y;
}
}

template<class Vector>
requires utils::point2d<Vector> || utils::junction<Vector> constexpr auto dotProduct(Vector* point_0, Vector* point_1) const noexcept
{
return std::get<"X">(*point_0) * std::get<"X">(*point_1) + std::get<"Y">(*point_0) * std::get<"Y">(*point_1);
}

template<class Point>
requires utils::point2d<Point> || utils::junction<Point>
constexpr auto isWithinAllowedDeviations(Point* A, Point* B, Point* C, Point* D, const std::floating_point auto fluid_angle, const std::integral auto max_resolution,
const std::floating_point auto AB_magnitude, const std::floating_point auto BC_magnitude, const std::floating_point auto CD_magnitude) const noexcept
{
if (BC_magnitude > max_resolution / 10) // TODO: make dedicated front-end setting for this
{
return true;
}
const double cos_A = std::acos(cosAngle(A, B, C, AB_magnitude, BC_magnitude));
const double cos_B = std::acos(cosAngle(A, B, D, AB_magnitude, CD_magnitude));
const auto abs_angle = std::abs(cos_A - cos_B);
return abs_angle < fluid_angle;
jellespijker marked this conversation as resolved.
Show resolved Hide resolved
}
};

inline constexpr smooth_fn smooth{};
} // namespace cura::actions

#endif // UTILS_VIEWS_SMOOTH_H
19 changes: 0 additions & 19 deletions include/utils/concepts/generic.h

This file was deleted.

145 changes: 145 additions & 0 deletions include/utils/types/arachne.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// Copyright (c) 2023 UltiMaker
// CuraEngine is released under the terms of the AGPLv3 or higher

#ifndef UTILS_TYPES_ARACHNE_H
#define UTILS_TYPES_ARACHNE_H

#include <concepts>
#include <string>
#include <type_traits>

#include <range/v3/range/concepts.hpp>

#include "utils/types/geometry.h"
#include "utils/types/generic.h"

namespace cura::utils
{
template<class T>
concept st_storable_data = requires(T val)
{
val.data;
};

/*!
* @brief A node in a skeleton trapezoidal graph, defined as a 2D point with additional stored data.
* @details This concept is used to check if a type is a node in a straight skeleton graph. A node is a type that is a 2D point with additional stored data.
* @tparam T Type to check
*/
template<class T>
concept st_node = requires(T val)
{
requires point2d<decltype(val.p)>;
};

/*!
* @brief A edge in a skeleton trapezoidal graph, defined as a 2D point with additional stored data.
* @details This concept is used to check if a type is a edge in a skeleton trapezoidal graph. defined as a pair of nodes with pointers to the next, previous, and twin edges, and additional stored data.
* @tparam T Type to check
*/
template<class T>
concept st_edge = requires(T val)
{
requires std::is_pointer_v<decltype(val.from)>;
requires st_node<decltype(*val.from)>;
requires std::is_pointer_v<decltype(val.to)>;
requires st_node<decltype(*val.to)>;
requires std::is_pointer_v<decltype(val.twin)>;
requires std::is_pointer_v<decltype(val.next)>;
requires std::is_pointer_v<decltype(val.prev)>;
};

template<class T>
concept st_edges = requires(T edges)
{
requires ranges::range<T>;
requires st_edge<decltype(*ranges::begin(edges))>;
requires st_storable_data<decltype(*ranges::begin(edges))>;
};

template<class T>
concept st_nodes = requires(T nodes)
{
requires ranges::range<T>;
requires st_node<decltype(*ranges::begin(nodes))>;
requires st_storable_data<decltype(*ranges::begin(nodes))>;
};

/*!
* @brief A skeleton trapezoidal graph, defined as a collection of nodes and edges.
* @details This concept is used to check if a type is a skeleton trapezoidal graph.
* @tparam T Type to check
*/
template<class T>
concept st_graph = requires(T graph)
{
requires st_edges<decltype(graph.edges)>;
requires st_nodes<decltype(graph.nodes)>;
};

/*!
* @brief A 2D point with an associated weight value
* @details This concept is used to check if a type is a junction as used in wall toolpaths
* @tparam T Type to check
*/
template<class T>
concept junction = requires(T val)
{
requires point2d<decltype(val.p)>;
requires std::integral<decltype(val.w)>;
};

/*!
* @brief A collection of junctions
* @details This concept is used to check if a type is a collection of junctions
* @tparam T Type to check
*/
template<class T>
concept junctions = requires(T val)
{
requires ranges::range<T>;
requires junction<decltype(*ranges::begin(val))>;
};

/*!
* @brief an Extrusion line in a toolpath
* @details This concept is used to check if a type is a collection of junctions. A series of junctions defining a path for extrusion,
* with additional flags indicating whether the path is closed and whether it corresponds to an odd or even layer.
* @tparam T Type to check
*/
template<class T>
concept extrusion_line = requires(T val)
{
std::is_same_v<decltype(val.inset_idx), size_t>;
std::is_same_v<decltype(val.is_odd), bool>;
std::is_same_v<decltype(val.is_closed), bool>;
requires junctions<decltype(val.junctions)>;
};

/*!
* @brief A collection of extrusion lines
* @details This concept is used to check if a type is a collection of extrusion lines
* @tparam T Type to check
*/
template<class T>
concept toolpath = requires(T tp)
{
requires ranges::range<T>;
requires extrusion_line<decltype(*ranges::begin(tp))>;
};

/*!
* @brief A collection of toolpaths
* @details This concept is used to check if a type is a collection of toolpaths
* @tparam T Type to check
*/
template<class T>
concept toolpaths = requires(T tp)
{
requires ranges::range<T>;
requires toolpath<decltype(*ranges::begin(tp))>;
};

} // namespace cura::utils

#endif // UTILS_TYPES_ARACHNE_H
25 changes: 25 additions & 0 deletions include/utils/types/char_range_literal.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) 2023 UltiMaker
// CuraEngine is released under the terms of the AGPLv3 or higher

#ifndef UTILS_TYPES_CHAR_RANGE_LITERAL_H
#define UTILS_TYPES_CHAR_RANGE_LITERAL_H

#include <algorithm>


namespace cura::utils
{
template<size_t N>
struct CharRangeLiteral
{
constexpr CharRangeLiteral(const char (&str)[N])
{
std::copy_n(str, N, value);
}

char value[N];
};

} // namespace cura::utils

#endif // UTILS_TYPES_CHAR_RANGE_LITERAL_H
Loading
Loading