mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-07-31 02:41:58 +08:00
Refactor: Move gcode travel utils to a separate file.
This commit is contained in:
parent
2175fc3f4d
commit
e13d3cdbf2
@ -192,6 +192,8 @@ set(SLIC3R_SOURCES
|
|||||||
GCode/GCodeProcessor.hpp
|
GCode/GCodeProcessor.hpp
|
||||||
GCode/AvoidCrossingPerimeters.cpp
|
GCode/AvoidCrossingPerimeters.cpp
|
||||||
GCode/AvoidCrossingPerimeters.hpp
|
GCode/AvoidCrossingPerimeters.hpp
|
||||||
|
GCode/Travels.cpp
|
||||||
|
GCode/Travels.hpp
|
||||||
GCode.cpp
|
GCode.cpp
|
||||||
GCode.hpp
|
GCode.hpp
|
||||||
GCodeReader.cpp
|
GCodeReader.cpp
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
#include "GCode/Thumbnails.hpp"
|
#include "GCode/Thumbnails.hpp"
|
||||||
#include "GCode/WipeTower.hpp"
|
#include "GCode/WipeTower.hpp"
|
||||||
#include "GCode/WipeTowerIntegration.hpp"
|
#include "GCode/WipeTowerIntegration.hpp"
|
||||||
|
#include "GCode/Travels.hpp"
|
||||||
#include "Point.hpp"
|
#include "Point.hpp"
|
||||||
#include "Polygon.hpp"
|
#include "Polygon.hpp"
|
||||||
#include "PrintConfig.hpp"
|
#include "PrintConfig.hpp"
|
||||||
@ -2737,7 +2738,7 @@ std::optional<std::string> GCodeGenerator::get_helical_layer_change_gcode(
|
|||||||
const double n_gon_circumference = unscaled(n_gon.length());
|
const double n_gon_circumference = unscaled(n_gon.length());
|
||||||
|
|
||||||
const double z_change{print_z - previous_layer_z};
|
const double z_change{print_z - previous_layer_z};
|
||||||
Points3 helix{GCode::Impl::generate_elevated_travel(
|
Points3 helix{GCode::Impl::Travels::generate_elevated_travel(
|
||||||
n_gon.points,
|
n_gon.points,
|
||||||
{},
|
{},
|
||||||
previous_layer_z,
|
previous_layer_z,
|
||||||
@ -3297,215 +3298,6 @@ std::string GCodeGenerator::_extrude(
|
|||||||
return gcode;
|
return gcode;
|
||||||
}
|
}
|
||||||
|
|
||||||
Points3 generate_flat_travel(tcb::span<const Point> xy_path, const float elevation) {
|
|
||||||
Points3 result;
|
|
||||||
result.reserve(xy_path.size() - 1);
|
|
||||||
for (const Point& point : xy_path.subspan(1)) {
|
|
||||||
result.emplace_back(point.x(), point.y(), scaled(elevation));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
Vec2d place_at_segment(const Vec2d& current_point, const Vec2d& previous_point, const double distance) {
|
|
||||||
Vec2d direction = (current_point - previous_point).normalized();
|
|
||||||
return previous_point + direction * distance;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace GCode::Impl {
|
|
||||||
std::vector<DistancedPoint> slice_xy_path(tcb::span<const Point> xy_path, tcb::span<const double> sorted_distances) {
|
|
||||||
assert(xy_path.size() >= 2);
|
|
||||||
std::vector<DistancedPoint> result;
|
|
||||||
result.reserve(xy_path.size() + sorted_distances.size());
|
|
||||||
double total_distance{0};
|
|
||||||
result.emplace_back(DistancedPoint{xy_path.front(), 0});
|
|
||||||
Point previous_point = result.front().point;
|
|
||||||
std::size_t offset{0};
|
|
||||||
for (const Point& point : xy_path.subspan(1)) {
|
|
||||||
Vec2d unscaled_point{unscaled(point)};
|
|
||||||
Vec2d unscaled_previous_point{unscaled(previous_point)};
|
|
||||||
const double current_segment_length = (unscaled_point - unscaled_previous_point).norm();
|
|
||||||
for (const double distance_to_add : sorted_distances.subspan(offset)) {
|
|
||||||
if (distance_to_add <= total_distance + current_segment_length) {
|
|
||||||
Point to_place = scaled(place_at_segment(
|
|
||||||
unscaled_point,
|
|
||||||
unscaled_previous_point,
|
|
||||||
distance_to_add - total_distance
|
|
||||||
));
|
|
||||||
if (to_place != previous_point && to_place != point) {
|
|
||||||
result.emplace_back(DistancedPoint{to_place, distance_to_add});
|
|
||||||
}
|
|
||||||
++offset;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
total_distance += current_segment_length;
|
|
||||||
result.emplace_back(DistancedPoint{point, total_distance});
|
|
||||||
previous_point = point;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ElevatedTravelParams {
|
|
||||||
double lift_height{};
|
|
||||||
double slope_end{};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ElevatedTravelFormula {
|
|
||||||
double operator()(double distance_from_start) const {
|
|
||||||
if (distance_from_start < this->params.slope_end) {
|
|
||||||
const double lift_percent = distance_from_start / this->params.slope_end;
|
|
||||||
return lift_percent * this->params.lift_height;
|
|
||||||
} else {
|
|
||||||
return this->params.lift_height;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ElevatedTravelParams params{};
|
|
||||||
};
|
|
||||||
|
|
||||||
Points3 generate_elevated_travel(
|
|
||||||
const tcb::span<const Point> xy_path,
|
|
||||||
const std::vector<double>& ensure_points_at_distances,
|
|
||||||
const double initial_elevation,
|
|
||||||
const std::function<double(double)>& elevation
|
|
||||||
) {
|
|
||||||
Points3 result{};
|
|
||||||
|
|
||||||
std::vector<DistancedPoint> extended_xy_path = slice_xy_path(xy_path, ensure_points_at_distances);
|
|
||||||
result.reserve(extended_xy_path.size());
|
|
||||||
|
|
||||||
for (const DistancedPoint& point : extended_xy_path) {
|
|
||||||
result.emplace_back(point.point.x(), point.point.y(), scaled(initial_elevation + elevation(point.distance_from_start)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
std::optional<double> get_first_crossed_line_distance(
|
|
||||||
tcb::span<const Line> xy_path,
|
|
||||||
const AABBTreeLines::LinesDistancer<Linef>& distancer
|
|
||||||
) {
|
|
||||||
assert(!xy_path.empty());
|
|
||||||
if (xy_path.empty()) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
double traversed_distance = 0;
|
|
||||||
for (const Line& line : xy_path) {
|
|
||||||
const Linef unscaled_line = {unscaled(line.a), unscaled(line.b)};
|
|
||||||
auto intersections = distancer.intersections_with_line<true>(unscaled_line);
|
|
||||||
if (!intersections.empty()) {
|
|
||||||
const Vec2d intersection = intersections.front().first;
|
|
||||||
const double distance = traversed_distance + (unscaled_line.a - intersection).norm();
|
|
||||||
if (distance > EPSILON) {
|
|
||||||
return distance;
|
|
||||||
} else if (intersections.size() >= 2) { // Edge case
|
|
||||||
const Vec2d second_intersection = intersections[1].first;
|
|
||||||
return traversed_distance + (unscaled_line.a - second_intersection).norm();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
traversed_distance += (unscaled_line.a - unscaled_line.b).norm();
|
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<double> get_obstacle_adjusted_slope_end(
|
|
||||||
const Lines& xy_path,
|
|
||||||
const std::optional<AABBTreeLines::LinesDistancer<Linef>>& previous_layer_distancer
|
|
||||||
) {
|
|
||||||
if (!previous_layer_distancer) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
std::optional<double> first_obstacle_distance = get_first_crossed_line_distance(
|
|
||||||
xy_path, *previous_layer_distancer
|
|
||||||
);
|
|
||||||
if (!first_obstacle_distance) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
return *first_obstacle_distance;
|
|
||||||
}
|
|
||||||
|
|
||||||
ElevatedTravelParams get_elevated_traval_params(
|
|
||||||
const Lines& xy_path,
|
|
||||||
const FullPrintConfig& config,
|
|
||||||
const unsigned extruder_id,
|
|
||||||
const std::optional<AABBTreeLines::LinesDistancer<Linef>>& previous_layer_distancer
|
|
||||||
) {
|
|
||||||
ElevatedTravelParams elevation_params{};
|
|
||||||
if (!config.travel_ramping_lift.get_at(extruder_id)) {
|
|
||||||
elevation_params.slope_end = 0;
|
|
||||||
elevation_params.lift_height = config.retract_lift.get_at(extruder_id);
|
|
||||||
return elevation_params;
|
|
||||||
}
|
|
||||||
elevation_params.lift_height = config.travel_max_lift.get_at(extruder_id);
|
|
||||||
|
|
||||||
const double slope_deg = config.travel_slope.get_at(extruder_id);
|
|
||||||
|
|
||||||
if (slope_deg >= 90 || slope_deg <= 0) {
|
|
||||||
elevation_params.slope_end = 0;
|
|
||||||
} else {
|
|
||||||
const double slope_rad = slope_deg * (M_PI / 180); // rad
|
|
||||||
elevation_params.slope_end = elevation_params.lift_height / std::tan(slope_rad);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<double> obstacle_adjusted_slope_end{get_obstacle_adjusted_slope_end(
|
|
||||||
xy_path,
|
|
||||||
previous_layer_distancer
|
|
||||||
)};
|
|
||||||
|
|
||||||
if (obstacle_adjusted_slope_end && obstacle_adjusted_slope_end < elevation_params.slope_end) {
|
|
||||||
elevation_params.slope_end = *obstacle_adjusted_slope_end;
|
|
||||||
}
|
|
||||||
|
|
||||||
return elevation_params;
|
|
||||||
}
|
|
||||||
|
|
||||||
Points3 generate_travel_to_extrusion(
|
|
||||||
const Polyline& xy_path,
|
|
||||||
const FullPrintConfig& config,
|
|
||||||
const unsigned extruder_id,
|
|
||||||
const double initial_elevation,
|
|
||||||
const std::optional<AABBTreeLines::LinesDistancer<Linef>>& previous_layer_distancer,
|
|
||||||
const Point& xy_path_coord_origin
|
|
||||||
) {
|
|
||||||
const double upper_limit = config.retract_lift_below.get_at(extruder_id);
|
|
||||||
const double lower_limit = config.retract_lift_above.get_at(extruder_id);
|
|
||||||
if (
|
|
||||||
(lower_limit > 0 && initial_elevation < lower_limit)
|
|
||||||
|| (upper_limit > 0 && initial_elevation > upper_limit)
|
|
||||||
) {
|
|
||||||
return generate_flat_travel(xy_path.points, initial_elevation);
|
|
||||||
}
|
|
||||||
|
|
||||||
Lines global_xy_path;
|
|
||||||
for (const Line& line : xy_path.lines()) {
|
|
||||||
global_xy_path.emplace_back(line.a + xy_path_coord_origin, line.b + xy_path_coord_origin);
|
|
||||||
}
|
|
||||||
|
|
||||||
ElevatedTravelParams elevation_params{get_elevated_traval_params(
|
|
||||||
global_xy_path,
|
|
||||||
config,
|
|
||||||
extruder_id,
|
|
||||||
previous_layer_distancer
|
|
||||||
)};
|
|
||||||
|
|
||||||
const std::vector<double> ensure_points_at_distances{elevation_params.slope_end};
|
|
||||||
|
|
||||||
Points3 result{generate_elevated_travel(
|
|
||||||
xy_path.points,
|
|
||||||
ensure_points_at_distances,
|
|
||||||
initial_elevation,
|
|
||||||
ElevatedTravelFormula{elevation_params}
|
|
||||||
)};
|
|
||||||
|
|
||||||
result.emplace_back(xy_path.back().x(), xy_path.back().y(), scaled(initial_elevation));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string GCodeGenerator::generate_travel_gcode(
|
std::string GCodeGenerator::generate_travel_gcode(
|
||||||
const Points3& travel,
|
const Points3& travel,
|
||||||
const std::string& comment
|
const std::string& comment
|
||||||
@ -3660,8 +3452,8 @@ std::string GCodeGenerator::travel_to(const Point &point, ExtrusionRole role, st
|
|||||||
const double initial_elevation = this->m_last_layer_z + this->m_config.z_offset.value;
|
const double initial_elevation = this->m_last_layer_z + this->m_config.z_offset.value;
|
||||||
const Points3 travel = (
|
const Points3 travel = (
|
||||||
can_be_flat ?
|
can_be_flat ?
|
||||||
generate_flat_travel(xy_path.points, initial_elevation) :
|
GCode::Impl::Travels::generate_flat_travel(xy_path.points, initial_elevation) :
|
||||||
GCode::Impl::generate_travel_to_extrusion(
|
GCode::Impl::Travels::generate_travel_to_extrusion(
|
||||||
xy_path,
|
xy_path,
|
||||||
this->m_config,
|
this->m_config,
|
||||||
extruder_id,
|
extruder_id,
|
||||||
|
@ -90,65 +90,6 @@ struct LayerResult {
|
|||||||
};
|
};
|
||||||
|
|
||||||
namespace GCode::Impl {
|
namespace GCode::Impl {
|
||||||
struct DistancedPoint {
|
|
||||||
Point point;
|
|
||||||
double distance_from_start;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Takes a path described as a list of points and adds points to it.
|
|
||||||
*
|
|
||||||
* @param xy_path A list of points describing a path in xy.
|
|
||||||
* @param sorted_distances A sorted list of distances along the path.
|
|
||||||
* @return Sliced path.
|
|
||||||
*
|
|
||||||
* The algorithm travels along the path segments and adds points to
|
|
||||||
* the segments in such a way that the points have specified distances
|
|
||||||
* from the xy_path start. **Any distances over the xy_path end will
|
|
||||||
* be simply ignored.**
|
|
||||||
*
|
|
||||||
* Example usage - simplified for clarity:
|
|
||||||
* @code
|
|
||||||
* std::vector<double> distances{0.5, 1.5};
|
|
||||||
* std::vector<Points> xy_path{{0, 0}, {1, 0}};
|
|
||||||
* // produces
|
|
||||||
* {{0, 0}, {0, 0.5}, {1, 0}}
|
|
||||||
* // notice that 1.5 is omitted
|
|
||||||
* @endcode
|
|
||||||
*/
|
|
||||||
std::vector<DistancedPoint> slice_xy_path(tcb::span<const Point> xy_path, tcb::span<const double> sorted_distances);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Take xy_path and genrate a travel acording to elevation.
|
|
||||||
*
|
|
||||||
* @param xy_path A list of points describing a path in xy.
|
|
||||||
* @param ensure_points_at_distances See slice_xy_path sorted_distances.
|
|
||||||
* @param elevation A function taking current distance in mm as input and returning elevation in mm as output.
|
|
||||||
*
|
|
||||||
* **Be aweare** that the elevation function operates in mm, while xy_path and returned travel are in
|
|
||||||
* scaled coordinates.
|
|
||||||
*/
|
|
||||||
Points3 generate_elevated_travel(
|
|
||||||
const tcb::span<const Point> xy_path,
|
|
||||||
const std::vector<double>& ensure_points_at_distances,
|
|
||||||
const double initial_elevation,
|
|
||||||
const std::function<double(double)>& elevation
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Given a AABB tree over lines find intersection with xy_path closest to the xy_path start.
|
|
||||||
*
|
|
||||||
* @param xy_path A path in 2D.
|
|
||||||
* @param distancer AABB Tree over lines.
|
|
||||||
* @return Distance to the first intersection if there is one.
|
|
||||||
*
|
|
||||||
* **Ignores intersection with xy_path starting point.**
|
|
||||||
*/
|
|
||||||
std::optional<double> get_first_crossed_line_distance(
|
|
||||||
tcb::span<const Line> xy_path,
|
|
||||||
const AABBTreeLines::LinesDistancer<Linef>& distancer
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a regular polygon - all angles are the same (e.g. typical hexagon).
|
* Generates a regular polygon - all angles are the same (e.g. typical hexagon).
|
||||||
|
205
src/libslic3r/GCode/Travels.cpp
Normal file
205
src/libslic3r/GCode/Travels.cpp
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
#include "Travels.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r::GCode::Impl::Travels {
|
||||||
|
|
||||||
|
Points3 generate_flat_travel(tcb::span<const Point> xy_path, const float elevation) {
|
||||||
|
Points3 result;
|
||||||
|
result.reserve(xy_path.size() - 1);
|
||||||
|
for (const Point &point : xy_path.subspan(1)) {
|
||||||
|
result.emplace_back(point.x(), point.y(), scaled(elevation));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2d place_at_segment(
|
||||||
|
const Vec2d ¤t_point, const Vec2d &previous_point, const double distance
|
||||||
|
) {
|
||||||
|
Vec2d direction = (current_point - previous_point).normalized();
|
||||||
|
return previous_point + direction * distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<DistancedPoint> slice_xy_path(
|
||||||
|
tcb::span<const Point> xy_path, tcb::span<const double> sorted_distances
|
||||||
|
) {
|
||||||
|
assert(xy_path.size() >= 2);
|
||||||
|
std::vector<DistancedPoint> result;
|
||||||
|
result.reserve(xy_path.size() + sorted_distances.size());
|
||||||
|
double total_distance{0};
|
||||||
|
result.emplace_back(DistancedPoint{xy_path.front(), 0});
|
||||||
|
Point previous_point = result.front().point;
|
||||||
|
std::size_t offset{0};
|
||||||
|
for (const Point &point : xy_path.subspan(1)) {
|
||||||
|
Vec2d unscaled_point{unscaled(point)};
|
||||||
|
Vec2d unscaled_previous_point{unscaled(previous_point)};
|
||||||
|
const double current_segment_length = (unscaled_point - unscaled_previous_point).norm();
|
||||||
|
for (const double distance_to_add : sorted_distances.subspan(offset)) {
|
||||||
|
if (distance_to_add <= total_distance + current_segment_length) {
|
||||||
|
Point to_place = scaled(place_at_segment(
|
||||||
|
unscaled_point, unscaled_previous_point, distance_to_add - total_distance
|
||||||
|
));
|
||||||
|
if (to_place != previous_point && to_place != point) {
|
||||||
|
result.emplace_back(DistancedPoint{to_place, distance_to_add});
|
||||||
|
}
|
||||||
|
++offset;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
total_distance += current_segment_length;
|
||||||
|
result.emplace_back(DistancedPoint{point, total_distance});
|
||||||
|
previous_point = point;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ElevatedTravelParams
|
||||||
|
{
|
||||||
|
double lift_height{};
|
||||||
|
double slope_end{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ElevatedTravelFormula
|
||||||
|
{
|
||||||
|
double operator()(double distance_from_start) const {
|
||||||
|
if (distance_from_start < this->params.slope_end) {
|
||||||
|
const double lift_percent = distance_from_start / this->params.slope_end;
|
||||||
|
return lift_percent * this->params.lift_height;
|
||||||
|
} else {
|
||||||
|
return this->params.lift_height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ElevatedTravelParams params{};
|
||||||
|
};
|
||||||
|
|
||||||
|
Points3 generate_elevated_travel(
|
||||||
|
const tcb::span<const Point> xy_path,
|
||||||
|
const std::vector<double> &ensure_points_at_distances,
|
||||||
|
const double initial_elevation,
|
||||||
|
const std::function<double(double)> &elevation
|
||||||
|
) {
|
||||||
|
Points3 result{};
|
||||||
|
|
||||||
|
std::vector<DistancedPoint> extended_xy_path = slice_xy_path(xy_path, ensure_points_at_distances);
|
||||||
|
result.reserve(extended_xy_path.size());
|
||||||
|
|
||||||
|
for (const DistancedPoint &point : extended_xy_path) {
|
||||||
|
result.emplace_back(
|
||||||
|
point.point.x(), point.point.y(),
|
||||||
|
scaled(initial_elevation + elevation(point.distance_from_start))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<double> get_first_crossed_line_distance(
|
||||||
|
tcb::span<const Line> xy_path, const AABBTreeLines::LinesDistancer<Linef> &distancer
|
||||||
|
) {
|
||||||
|
assert(!xy_path.empty());
|
||||||
|
if (xy_path.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
double traversed_distance = 0;
|
||||||
|
for (const Line &line : xy_path) {
|
||||||
|
const Linef unscaled_line = {unscaled(line.a), unscaled(line.b)};
|
||||||
|
auto intersections = distancer.intersections_with_line<true>(unscaled_line);
|
||||||
|
if (!intersections.empty()) {
|
||||||
|
const Vec2d intersection = intersections.front().first;
|
||||||
|
const double distance = traversed_distance + (unscaled_line.a - intersection).norm();
|
||||||
|
if (distance > EPSILON) {
|
||||||
|
return distance;
|
||||||
|
} else if (intersections.size() >= 2) { // Edge case
|
||||||
|
const Vec2d second_intersection = intersections[1].first;
|
||||||
|
return traversed_distance + (unscaled_line.a - second_intersection).norm();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
traversed_distance += (unscaled_line.a - unscaled_line.b).norm();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<double> get_obstacle_adjusted_slope_end(
|
||||||
|
const Lines &xy_path,
|
||||||
|
const std::optional<AABBTreeLines::LinesDistancer<Linef>> &previous_layer_distancer
|
||||||
|
) {
|
||||||
|
if (!previous_layer_distancer) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
std::optional<double> first_obstacle_distance =
|
||||||
|
get_first_crossed_line_distance(xy_path, *previous_layer_distancer);
|
||||||
|
if (!first_obstacle_distance) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return *first_obstacle_distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
ElevatedTravelParams get_elevated_traval_params(
|
||||||
|
const Lines &xy_path,
|
||||||
|
const FullPrintConfig &config,
|
||||||
|
const unsigned extruder_id,
|
||||||
|
const std::optional<AABBTreeLines::LinesDistancer<Linef>> &previous_layer_distancer
|
||||||
|
) {
|
||||||
|
ElevatedTravelParams elevation_params{};
|
||||||
|
if (!config.travel_ramping_lift.get_at(extruder_id)) {
|
||||||
|
elevation_params.slope_end = 0;
|
||||||
|
elevation_params.lift_height = config.retract_lift.get_at(extruder_id);
|
||||||
|
return elevation_params;
|
||||||
|
}
|
||||||
|
elevation_params.lift_height = config.travel_max_lift.get_at(extruder_id);
|
||||||
|
|
||||||
|
const double slope_deg = config.travel_slope.get_at(extruder_id);
|
||||||
|
|
||||||
|
if (slope_deg >= 90 || slope_deg <= 0) {
|
||||||
|
elevation_params.slope_end = 0;
|
||||||
|
} else {
|
||||||
|
const double slope_rad = slope_deg * (M_PI / 180); // rad
|
||||||
|
elevation_params.slope_end = elevation_params.lift_height / std::tan(slope_rad);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<double> obstacle_adjusted_slope_end{
|
||||||
|
get_obstacle_adjusted_slope_end(xy_path, previous_layer_distancer)};
|
||||||
|
|
||||||
|
if (obstacle_adjusted_slope_end && obstacle_adjusted_slope_end < elevation_params.slope_end) {
|
||||||
|
elevation_params.slope_end = *obstacle_adjusted_slope_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
return elevation_params;
|
||||||
|
}
|
||||||
|
|
||||||
|
Points3 generate_travel_to_extrusion(
|
||||||
|
const Polyline &xy_path,
|
||||||
|
const FullPrintConfig &config,
|
||||||
|
const unsigned extruder_id,
|
||||||
|
const double initial_elevation,
|
||||||
|
const std::optional<AABBTreeLines::LinesDistancer<Linef>> &previous_layer_distancer,
|
||||||
|
const Point &xy_path_coord_origin
|
||||||
|
) {
|
||||||
|
const double upper_limit = config.retract_lift_below.get_at(extruder_id);
|
||||||
|
const double lower_limit = config.retract_lift_above.get_at(extruder_id);
|
||||||
|
if ((lower_limit > 0 && initial_elevation < lower_limit) ||
|
||||||
|
(upper_limit > 0 && initial_elevation > upper_limit)) {
|
||||||
|
return generate_flat_travel(xy_path.points, initial_elevation);
|
||||||
|
}
|
||||||
|
|
||||||
|
Lines global_xy_path;
|
||||||
|
for (const Line &line : xy_path.lines()) {
|
||||||
|
global_xy_path.emplace_back(line.a + xy_path_coord_origin, line.b + xy_path_coord_origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
ElevatedTravelParams elevation_params{
|
||||||
|
get_elevated_traval_params(global_xy_path, config, extruder_id, previous_layer_distancer)};
|
||||||
|
|
||||||
|
const std::vector<double> ensure_points_at_distances{elevation_params.slope_end};
|
||||||
|
|
||||||
|
Points3 result{generate_elevated_travel(
|
||||||
|
xy_path.points, ensure_points_at_distances, initial_elevation,
|
||||||
|
ElevatedTravelFormula{elevation_params}
|
||||||
|
)};
|
||||||
|
|
||||||
|
result.emplace_back(xy_path.back().x(), xy_path.back().y(), scaled(initial_elevation));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} // namespace Slic3r::GCode::Impl::Travels
|
91
src/libslic3r/GCode/Travels.hpp
Normal file
91
src/libslic3r/GCode/Travels.hpp
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
#include <vector>
|
||||||
|
#include <tcbspan/span.hpp>
|
||||||
|
#include <functional>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include "libslic3r/Line.hpp"
|
||||||
|
#include "libslic3r/Point.hpp"
|
||||||
|
#include "libslic3r/AABBTreeLines.hpp"
|
||||||
|
#include "libslic3r/PrintConfig.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r::GCode::Impl::Travels {
|
||||||
|
struct DistancedPoint
|
||||||
|
{
|
||||||
|
Point point;
|
||||||
|
double distance_from_start;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Takes a path described as a list of points and adds points to it.
|
||||||
|
*
|
||||||
|
* @param xy_path A list of points describing a path in xy.
|
||||||
|
* @param sorted_distances A sorted list of distances along the path.
|
||||||
|
* @return Sliced path.
|
||||||
|
*
|
||||||
|
* The algorithm travels along the path segments and adds points to
|
||||||
|
* the segments in such a way that the points have specified distances
|
||||||
|
* from the xy_path start. **Any distances over the xy_path end will
|
||||||
|
* be simply ignored.**
|
||||||
|
*
|
||||||
|
* Example usage - simplified for clarity:
|
||||||
|
* @code
|
||||||
|
* std::vector<double> distances{0.5, 1.5};
|
||||||
|
* std::vector<Points> xy_path{{0, 0}, {1, 0}};
|
||||||
|
* // produces
|
||||||
|
* {{0, 0}, {0, 0.5}, {1, 0}}
|
||||||
|
* // notice that 1.5 is omitted
|
||||||
|
* @endcode
|
||||||
|
*/
|
||||||
|
std::vector<DistancedPoint> slice_xy_path(
|
||||||
|
tcb::span<const Point> xy_path, tcb::span<const double> sorted_distances
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Simply return the xy_path with z coord set to elevation.
|
||||||
|
*/
|
||||||
|
Points3 generate_flat_travel(tcb::span<const Point> xy_path, const float elevation);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Take xy_path and genrate a travel acording to elevation.
|
||||||
|
*
|
||||||
|
* @param xy_path A list of points describing a path in xy.
|
||||||
|
* @param ensure_points_at_distances See slice_xy_path sorted_distances.
|
||||||
|
* @param elevation A function taking current distance in mm as input and returning elevation in mm
|
||||||
|
* as output.
|
||||||
|
*
|
||||||
|
* **Be aweare** that the elevation function operates in mm, while xy_path and returned travel are
|
||||||
|
* in scaled coordinates.
|
||||||
|
*/
|
||||||
|
Points3 generate_elevated_travel(
|
||||||
|
const tcb::span<const Point> xy_path,
|
||||||
|
const std::vector<double> &ensure_points_at_distances,
|
||||||
|
const double initial_elevation,
|
||||||
|
const std::function<double(double)> &elevation
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Given a AABB tree over lines find intersection with xy_path closest to the xy_path start.
|
||||||
|
*
|
||||||
|
* @param xy_path A path in 2D.
|
||||||
|
* @param distancer AABB Tree over lines.
|
||||||
|
* @return Distance to the first intersection if there is one.
|
||||||
|
*
|
||||||
|
* **Ignores intersection with xy_path starting point.**
|
||||||
|
*/
|
||||||
|
std::optional<double> get_first_crossed_line_distance(
|
||||||
|
tcb::span<const Line> xy_path, const AABBTreeLines::LinesDistancer<Linef> &distancer
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Extract parameters and decide wheather the travel can be elevated.
|
||||||
|
* Then generate the whole travel 3D path - elevated if possible.
|
||||||
|
*/
|
||||||
|
Points3 generate_travel_to_extrusion(
|
||||||
|
const Polyline &xy_path,
|
||||||
|
const FullPrintConfig &config,
|
||||||
|
const unsigned extruder_id,
|
||||||
|
const double initial_elevation,
|
||||||
|
const std::optional<AABBTreeLines::LinesDistancer<Linef>> &previous_layer_distancer,
|
||||||
|
const Point &xy_path_coord_origin
|
||||||
|
);
|
||||||
|
} // namespace Slic3r::GCode::Impl::Travels
|
@ -13,6 +13,7 @@ add_executable(${_TEST_NAME}_tests
|
|||||||
test_flow.cpp
|
test_flow.cpp
|
||||||
test_gaps.cpp
|
test_gaps.cpp
|
||||||
test_gcode.cpp
|
test_gcode.cpp
|
||||||
|
test_gcode_travels.cpp
|
||||||
test_gcodefindreplace.cpp
|
test_gcodefindreplace.cpp
|
||||||
test_gcodewriter.cpp
|
test_gcodewriter.cpp
|
||||||
test_model.cpp
|
test_model.cpp
|
||||||
|
@ -22,180 +22,6 @@ SCENARIO("Origin manipulation", "[GCode]") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ApproxEqualsPoints : public Catch::MatcherBase<Points> {
|
|
||||||
ApproxEqualsPoints(const Points& expected, unsigned tolerance): expected(expected), tolerance(tolerance) {}
|
|
||||||
bool match(const Points& points) const override {
|
|
||||||
if (points.size() != expected.size()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (auto i = 0u; i < points.size(); ++i) {
|
|
||||||
const Point& point = points[i];
|
|
||||||
const Point& expected_point = this->expected[i];
|
|
||||||
if (
|
|
||||||
std::abs(point.x() - expected_point.x()) > this->tolerance
|
|
||||||
|| std::abs(point.y() - expected_point.y()) > this->tolerance
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
std::string describe() const override {
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << std::endl;
|
|
||||||
for (const Point& point : expected) {
|
|
||||||
ss << "(" << point.x() << ", " << point.y() << ")" << std::endl;
|
|
||||||
}
|
|
||||||
ss << "With tolerance: " << this->tolerance;
|
|
||||||
|
|
||||||
return "Equals " + ss.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
Points expected;
|
|
||||||
unsigned tolerance;
|
|
||||||
};
|
|
||||||
|
|
||||||
Points get_points(const std::vector<DistancedPoint>& result) {
|
|
||||||
Points result_points;
|
|
||||||
std::transform(
|
|
||||||
result.begin(),
|
|
||||||
result.end(),
|
|
||||||
std::back_inserter(result_points),
|
|
||||||
[](const DistancedPoint& point){
|
|
||||||
return point.point;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return result_points;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<double> get_distances(const std::vector<DistancedPoint>& result) {
|
|
||||||
std::vector<double> result_distances;
|
|
||||||
std::transform(
|
|
||||||
result.begin(),
|
|
||||||
result.end(),
|
|
||||||
std::back_inserter(result_distances),
|
|
||||||
[](const DistancedPoint& point){
|
|
||||||
return point.distance_from_start;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return result_distances;
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Place points at distances - expected use", "[GCode]") {
|
|
||||||
std::vector<Point> line{
|
|
||||||
scaled(Vec2f{0, 0}),
|
|
||||||
scaled(Vec2f{1, 0}),
|
|
||||||
scaled(Vec2f{2, 1}),
|
|
||||||
scaled(Vec2f{2, 2})
|
|
||||||
};
|
|
||||||
std::vector<double> distances{0, 0.2, 0.5, 1 + std::sqrt(2)/2, 1 + std::sqrt(2) + 0.5, 100.0};
|
|
||||||
std::vector<DistancedPoint> result = slice_xy_path(line, distances);
|
|
||||||
|
|
||||||
REQUIRE_THAT(get_points(result), ApproxEqualsPoints(Points{
|
|
||||||
scaled(Vec2f{0, 0}),
|
|
||||||
scaled(Vec2f{0.2, 0}),
|
|
||||||
scaled(Vec2f{0.5, 0}),
|
|
||||||
scaled(Vec2f{1, 0}),
|
|
||||||
scaled(Vec2f{1.5, 0.5}),
|
|
||||||
scaled(Vec2f{2, 1}),
|
|
||||||
scaled(Vec2f{2, 1.5}),
|
|
||||||
scaled(Vec2f{2, 2})
|
|
||||||
}, 5));
|
|
||||||
|
|
||||||
REQUIRE_THAT(get_distances(result), Catch::Matchers::Approx(std::vector<double>{
|
|
||||||
distances[0], distances[1], distances[2], 1, distances[3], 1 + std::sqrt(2), distances[4], 2 + std::sqrt(2)
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Place points at distances - edge case", "[GCode]") {
|
|
||||||
std::vector<Point> line{
|
|
||||||
scaled(Vec2f{0, 0}),
|
|
||||||
scaled(Vec2f{1, 0}),
|
|
||||||
scaled(Vec2f{2, 0})
|
|
||||||
};
|
|
||||||
std::vector<double> distances{0, 1, 1.5, 2};
|
|
||||||
Points result{get_points(slice_xy_path(line, distances))};
|
|
||||||
CHECK(result == Points{
|
|
||||||
scaled(Vec2f{0, 0}),
|
|
||||||
scaled(Vec2f{1, 0}),
|
|
||||||
scaled(Vec2f{1.5, 0}),
|
|
||||||
scaled(Vec2f{2, 0})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Generate elevated travel", "[GCode]") {
|
|
||||||
std::vector<Point> xy_path{
|
|
||||||
scaled(Vec2f{0, 0}),
|
|
||||||
scaled(Vec2f{1, 0}),
|
|
||||||
};
|
|
||||||
std::vector<double> ensure_points_at_distances{0.2, 0.5};
|
|
||||||
Points3 result{generate_elevated_travel(xy_path, ensure_points_at_distances, 2.0, [](double x){return 1 + x;})};
|
|
||||||
|
|
||||||
CHECK(result == Points3{
|
|
||||||
scaled(Vec3f{0, 0, 3.0}),
|
|
||||||
scaled(Vec3f{0.2, 0, 3.2}),
|
|
||||||
scaled(Vec3f{0.5, 0, 3.5}),
|
|
||||||
scaled(Vec3f{1, 0, 4.0})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Get first crossed line distance", "[GCode]") {
|
|
||||||
// A 2x2 square at 0, 0, with 1x1 square hole in its center.
|
|
||||||
ExPolygon square_with_hole{
|
|
||||||
{
|
|
||||||
scaled(Vec2f{-1, -1}),
|
|
||||||
scaled(Vec2f{1, -1}),
|
|
||||||
scaled(Vec2f{1, 1}),
|
|
||||||
scaled(Vec2f{-1, 1})
|
|
||||||
},
|
|
||||||
{
|
|
||||||
scaled(Vec2f{-0.5, -0.5}),
|
|
||||||
scaled(Vec2f{0.5, -0.5}),
|
|
||||||
scaled(Vec2f{0.5, 0.5}),
|
|
||||||
scaled(Vec2f{-0.5, 0.5})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// A 2x2 square above the previous square at (0, 3).
|
|
||||||
ExPolygon square_above{
|
|
||||||
{
|
|
||||||
scaled(Vec2f{-1, 2}),
|
|
||||||
scaled(Vec2f{1, 2}),
|
|
||||||
scaled(Vec2f{1, 4}),
|
|
||||||
scaled(Vec2f{-1, 4})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Bottom-up travel intersecting the squares.
|
|
||||||
Lines travel{Polyline{
|
|
||||||
scaled(Vec2f{0, -2}),
|
|
||||||
scaled(Vec2f{0, -0.7}),
|
|
||||||
scaled(Vec2f{0, 0}),
|
|
||||||
scaled(Vec2f{0, 1}),
|
|
||||||
scaled(Vec2f{0, 1.3}),
|
|
||||||
scaled(Vec2f{0, 2.4}),
|
|
||||||
scaled(Vec2f{0, 4.5}),
|
|
||||||
scaled(Vec2f{0, 5}),
|
|
||||||
}.lines()};
|
|
||||||
|
|
||||||
std::vector<Linef> lines;
|
|
||||||
for (const ExPolygon& polygon : {square_with_hole, square_above}) {
|
|
||||||
for (const Line& line : polygon.lines()) {
|
|
||||||
lines.emplace_back(unscale(line.a), unscale(line.b));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Try different cases by skipping lines in the travel.
|
|
||||||
AABBTreeLines::LinesDistancer<Linef> distancer{std::move(lines)};
|
|
||||||
|
|
||||||
CHECK(*get_first_crossed_line_distance(travel, distancer) == Approx(1));
|
|
||||||
CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(1), distancer) == Approx(0.2));
|
|
||||||
CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(2), distancer) == Approx(0.5));
|
|
||||||
CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(3), distancer) == Approx(1.0)); //Edge case
|
|
||||||
CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(4), distancer) == Approx(0.7));
|
|
||||||
CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(5), distancer) == Approx(1.6));
|
|
||||||
CHECK_FALSE(get_first_crossed_line_distance(tcb::span{travel}.subspan(6), distancer));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Generate regular polygon", "[GCode]") {
|
TEST_CASE("Generate regular polygon", "[GCode]") {
|
||||||
const unsigned points_count{32};
|
const unsigned points_count{32};
|
||||||
const Point centroid{scaled(Vec2d{5, -2})};
|
const Point centroid{scaled(Vec2d{5, -2})};
|
||||||
|
181
tests/fff_print/test_gcode_travels.cpp
Normal file
181
tests/fff_print/test_gcode_travels.cpp
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
#include <catch2/catch.hpp>
|
||||||
|
#include <libslic3r/GCode/Travels.hpp>
|
||||||
|
#include <libslic3r/ExPolygon.hpp>
|
||||||
|
|
||||||
|
using namespace Slic3r;
|
||||||
|
using namespace Slic3r::GCode::Impl::Travels;
|
||||||
|
|
||||||
|
struct ApproxEqualsPoints : public Catch::MatcherBase<Points> {
|
||||||
|
ApproxEqualsPoints(const Points& expected, unsigned tolerance): expected(expected), tolerance(tolerance) {}
|
||||||
|
bool match(const Points& points) const override {
|
||||||
|
if (points.size() != expected.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (auto i = 0u; i < points.size(); ++i) {
|
||||||
|
const Point& point = points[i];
|
||||||
|
const Point& expected_point = this->expected[i];
|
||||||
|
if (
|
||||||
|
std::abs(point.x() - expected_point.x()) > this->tolerance
|
||||||
|
|| std::abs(point.y() - expected_point.y()) > this->tolerance
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
std::string describe() const override {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << std::endl;
|
||||||
|
for (const Point& point : expected) {
|
||||||
|
ss << "(" << point.x() << ", " << point.y() << ")" << std::endl;
|
||||||
|
}
|
||||||
|
ss << "With tolerance: " << this->tolerance;
|
||||||
|
|
||||||
|
return "Equals " + ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Points expected;
|
||||||
|
unsigned tolerance;
|
||||||
|
};
|
||||||
|
|
||||||
|
Points get_points(const std::vector<DistancedPoint>& result) {
|
||||||
|
Points result_points;
|
||||||
|
std::transform(
|
||||||
|
result.begin(),
|
||||||
|
result.end(),
|
||||||
|
std::back_inserter(result_points),
|
||||||
|
[](const DistancedPoint& point){
|
||||||
|
return point.point;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return result_points;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<double> get_distances(const std::vector<DistancedPoint>& result) {
|
||||||
|
std::vector<double> result_distances;
|
||||||
|
std::transform(
|
||||||
|
result.begin(),
|
||||||
|
result.end(),
|
||||||
|
std::back_inserter(result_distances),
|
||||||
|
[](const DistancedPoint& point){
|
||||||
|
return point.distance_from_start;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return result_distances;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Place points at distances - expected use", "[GCode]") {
|
||||||
|
std::vector<Point> line{
|
||||||
|
scaled(Vec2f{0, 0}),
|
||||||
|
scaled(Vec2f{1, 0}),
|
||||||
|
scaled(Vec2f{2, 1}),
|
||||||
|
scaled(Vec2f{2, 2})
|
||||||
|
};
|
||||||
|
std::vector<double> distances{0, 0.2, 0.5, 1 + std::sqrt(2)/2, 1 + std::sqrt(2) + 0.5, 100.0};
|
||||||
|
std::vector<DistancedPoint> result = slice_xy_path(line, distances);
|
||||||
|
|
||||||
|
REQUIRE_THAT(get_points(result), ApproxEqualsPoints(Points{
|
||||||
|
scaled(Vec2f{0, 0}),
|
||||||
|
scaled(Vec2f{0.2, 0}),
|
||||||
|
scaled(Vec2f{0.5, 0}),
|
||||||
|
scaled(Vec2f{1, 0}),
|
||||||
|
scaled(Vec2f{1.5, 0.5}),
|
||||||
|
scaled(Vec2f{2, 1}),
|
||||||
|
scaled(Vec2f{2, 1.5}),
|
||||||
|
scaled(Vec2f{2, 2})
|
||||||
|
}, 5));
|
||||||
|
|
||||||
|
REQUIRE_THAT(get_distances(result), Catch::Matchers::Approx(std::vector<double>{
|
||||||
|
distances[0], distances[1], distances[2], 1, distances[3], 1 + std::sqrt(2), distances[4], 2 + std::sqrt(2)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Place points at distances - edge case", "[GCode]") {
|
||||||
|
std::vector<Point> line{
|
||||||
|
scaled(Vec2f{0, 0}),
|
||||||
|
scaled(Vec2f{1, 0}),
|
||||||
|
scaled(Vec2f{2, 0})
|
||||||
|
};
|
||||||
|
std::vector<double> distances{0, 1, 1.5, 2};
|
||||||
|
Points result{get_points(slice_xy_path(line, distances))};
|
||||||
|
CHECK(result == Points{
|
||||||
|
scaled(Vec2f{0, 0}),
|
||||||
|
scaled(Vec2f{1, 0}),
|
||||||
|
scaled(Vec2f{1.5, 0}),
|
||||||
|
scaled(Vec2f{2, 0})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Generate elevated travel", "[GCode]") {
|
||||||
|
std::vector<Point> xy_path{
|
||||||
|
scaled(Vec2f{0, 0}),
|
||||||
|
scaled(Vec2f{1, 0}),
|
||||||
|
};
|
||||||
|
std::vector<double> ensure_points_at_distances{0.2, 0.5};
|
||||||
|
Points3 result{generate_elevated_travel(xy_path, ensure_points_at_distances, 2.0, [](double x){return 1 + x;})};
|
||||||
|
|
||||||
|
CHECK(result == Points3{
|
||||||
|
scaled(Vec3f{0, 0, 3.0}),
|
||||||
|
scaled(Vec3f{0.2, 0, 3.2}),
|
||||||
|
scaled(Vec3f{0.5, 0, 3.5}),
|
||||||
|
scaled(Vec3f{1, 0, 4.0})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Get first crossed line distance", "[GCode]") {
|
||||||
|
// A 2x2 square at 0, 0, with 1x1 square hole in its center.
|
||||||
|
ExPolygon square_with_hole{
|
||||||
|
{
|
||||||
|
scaled(Vec2f{-1, -1}),
|
||||||
|
scaled(Vec2f{1, -1}),
|
||||||
|
scaled(Vec2f{1, 1}),
|
||||||
|
scaled(Vec2f{-1, 1})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scaled(Vec2f{-0.5, -0.5}),
|
||||||
|
scaled(Vec2f{0.5, -0.5}),
|
||||||
|
scaled(Vec2f{0.5, 0.5}),
|
||||||
|
scaled(Vec2f{-0.5, 0.5})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// A 2x2 square above the previous square at (0, 3).
|
||||||
|
ExPolygon square_above{
|
||||||
|
{
|
||||||
|
scaled(Vec2f{-1, 2}),
|
||||||
|
scaled(Vec2f{1, 2}),
|
||||||
|
scaled(Vec2f{1, 4}),
|
||||||
|
scaled(Vec2f{-1, 4})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Bottom-up travel intersecting the squares.
|
||||||
|
Lines travel{Polyline{
|
||||||
|
scaled(Vec2f{0, -2}),
|
||||||
|
scaled(Vec2f{0, -0.7}),
|
||||||
|
scaled(Vec2f{0, 0}),
|
||||||
|
scaled(Vec2f{0, 1}),
|
||||||
|
scaled(Vec2f{0, 1.3}),
|
||||||
|
scaled(Vec2f{0, 2.4}),
|
||||||
|
scaled(Vec2f{0, 4.5}),
|
||||||
|
scaled(Vec2f{0, 5}),
|
||||||
|
}.lines()};
|
||||||
|
|
||||||
|
std::vector<Linef> lines;
|
||||||
|
for (const ExPolygon& polygon : {square_with_hole, square_above}) {
|
||||||
|
for (const Line& line : polygon.lines()) {
|
||||||
|
lines.emplace_back(unscale(line.a), unscale(line.b));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Try different cases by skipping lines in the travel.
|
||||||
|
AABBTreeLines::LinesDistancer<Linef> distancer{std::move(lines)};
|
||||||
|
|
||||||
|
CHECK(*get_first_crossed_line_distance(travel, distancer) == Approx(1));
|
||||||
|
CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(1), distancer) == Approx(0.2));
|
||||||
|
CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(2), distancer) == Approx(0.5));
|
||||||
|
CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(3), distancer) == Approx(1.0)); //Edge case
|
||||||
|
CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(4), distancer) == Approx(0.7));
|
||||||
|
CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(5), distancer) == Approx(1.6));
|
||||||
|
CHECK_FALSE(get_first_crossed_line_distance(tcb::span{travel}.subspan(6), distancer));
|
||||||
|
}
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user