Replace GCode.cpp travel_to with more general z-hop strategy.

The new travel has an initial flat part, sloped part and
once the travel height reaches maxima a flat part again.
Also, the notion of extruder lift is removed. It is used no more.
Consequently the retract_lift parameter lost its original meaning.
This commit is contained in:
Martin Šach 2023-11-07 16:12:11 +01:00 committed by SachCZ
parent 09466df1d4
commit 49455cf427
12 changed files with 544 additions and 266 deletions

View File

@ -141,11 +141,6 @@ double Extruder::retract_length() const
return m_config->retract_length.get_at(m_id); return m_config->retract_length.get_at(m_id);
} }
double Extruder::retract_lift() const
{
return m_config->retract_lift.get_at(m_id);
}
int Extruder::retract_speed() const int Extruder::retract_speed() const
{ {
return int(floor(m_config->retract_speed.get_at(m_id)+0.5)); return int(floor(m_config->retract_speed.get_at(m_id)+0.5));

View File

@ -182,7 +182,7 @@ void GCodeGenerator::PlaceholderParserIntegration::reset()
this->failed_templates.clear(); this->failed_templates.clear();
this->output_config.clear(); this->output_config.clear();
this->opt_position = nullptr; this->opt_position = nullptr;
this->opt_zhop = nullptr; this->opt_zhop = nullptr;
this->opt_e_position = nullptr; this->opt_e_position = nullptr;
this->opt_e_retracted = nullptr; this->opt_e_retracted = nullptr;
this->opt_e_restart_extra = nullptr; this->opt_e_restart_extra = nullptr;
@ -228,6 +228,7 @@ void GCodeGenerator::PlaceholderParserIntegration::init(const GCodeWriter &write
this->position.assign(3, 0); this->position.assign(3, 0);
this->opt_position = new ConfigOptionFloats(this->position); this->opt_position = new ConfigOptionFloats(this->position);
this->output_config.set_key_value("position", this->opt_position); this->output_config.set_key_value("position", this->opt_position);
// Store zhop variable into the parser itself, it is a read-only variable to the script. // Store zhop variable into the parser itself, it is a read-only variable to the script.
this->opt_zhop = new ConfigOptionFloat(writer.get_zhop()); this->opt_zhop = new ConfigOptionFloat(writer.get_zhop());
this->parser.set("zhop", this->opt_zhop); this->parser.set("zhop", this->opt_zhop);
@ -237,7 +238,6 @@ void GCodeGenerator::PlaceholderParserIntegration::update_from_gcodewriter(const
{ {
memcpy(this->position.data(), writer.get_position().data(), sizeof(double) * 3); memcpy(this->position.data(), writer.get_position().data(), sizeof(double) * 3);
this->opt_position->values = this->position; this->opt_position->values = this->position;
this->opt_zhop->value = writer.get_zhop();
if (this->num_extruders > 0) { if (this->num_extruders > 0) {
const std::vector<Extruder> &extruders = writer.extruders(); const std::vector<Extruder> &extruders = writer.extruders();
@ -1257,7 +1257,7 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
// This happens before Z goes down to layer 0 again, so that no collision happens hopefully. // This happens before Z goes down to layer 0 again, so that no collision happens hopefully.
m_enable_cooling_markers = false; // we're not filtering these moves through CoolingBuffer m_enable_cooling_markers = false; // we're not filtering these moves through CoolingBuffer
m_avoid_crossing_perimeters.use_external_mp_once(); m_avoid_crossing_perimeters.use_external_mp_once();
file.write(this->retract()); file.write(this->retract_and_wipe());
file.write(this->travel_to(Point(0, 0), ExtrusionRole::None, "move to origin position for next object")); file.write(this->travel_to(Point(0, 0), ExtrusionRole::None, "move to origin position for next object"));
m_enable_cooling_markers = true; m_enable_cooling_markers = true;
// Disable motion planner when traveling to first object point. // Disable motion planner when traveling to first object point.
@ -1308,7 +1308,7 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
bool overlap = bbox_prime.overlap(bbox_print); bool overlap = bbox_prime.overlap(bbox_print);
if (print.config().gcode_flavor == gcfMarlinLegacy || print.config().gcode_flavor == gcfMarlinFirmware) { if (print.config().gcode_flavor == gcfMarlinLegacy || print.config().gcode_flavor == gcfMarlinFirmware) {
file.write(this->retract()); file.write(this->retract_and_wipe());
file.write("M300 S800 P500\n"); // Beep for 500ms, tone 800Hz. file.write("M300 S800 P500\n"); // Beep for 500ms, tone 800Hz.
if (overlap) { if (overlap) {
// Wait for the user to remove the priming extrusions. // Wait for the user to remove the priming extrusions.
@ -1344,7 +1344,7 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
} }
// Write end commands to file. // Write end commands to file.
file.write(this->retract()); file.write(this->retract_and_wipe());
file.write(m_writer.set_fan(0)); file.write(m_writer.set_fan(0));
// adds tag for processor // adds tag for processor
@ -2616,8 +2616,8 @@ std::string GCodeGenerator::change_layer(coordf_t print_z)
// Increment a progress bar indicator. // Increment a progress bar indicator.
gcode += m_writer.update_progress(++ m_layer_index, m_layer_count); gcode += m_writer.update_progress(++ m_layer_index, m_layer_count);
coordf_t z = print_z + m_config.z_offset.value; // in unscaled coordinates coordf_t z = print_z + m_config.z_offset.value; // in unscaled coordinates
if (EXTRUDER_CONFIG(retract_layer_change) && m_writer.will_move_z(z)) if (EXTRUDER_CONFIG(retract_layer_change))
gcode += this->retract(); gcode += this->retract_and_wipe();
{ {
std::ostringstream comment; std::ostringstream comment;
@ -2688,9 +2688,11 @@ std::string GCodeGenerator::extrude_loop(const ExtrusionLoop &loop_src, const GC
} else if (loop_src.paths.back().role().is_external_perimeter() && m_layer != nullptr && m_config.perimeters.value > 1) { } else if (loop_src.paths.back().role().is_external_perimeter() && m_layer != nullptr && m_config.perimeters.value > 1) {
// Only wipe inside if the wipe along the perimeter is disabled. // Only wipe inside if the wipe along the perimeter is disabled.
// Make a little move inwards before leaving loop. // Make a little move inwards before leaving loop.
if (std::optional<Point> pt = wipe_hide_seam(smooth_path, is_hole, scale_(EXTRUDER_CONFIG(nozzle_diameter))); pt) if (std::optional<Point> pt = wipe_hide_seam(smooth_path, is_hole, scale_(EXTRUDER_CONFIG(nozzle_diameter))); pt) {
// Generate the seam hiding travel move. // Generate the seam hiding travel move.
gcode += m_writer.travel_to_xy(this->point_to_gcode(*pt), "move inwards before travel"); gcode += m_writer.travel_to_xy(this->point_to_gcode(*pt), "move inwards before travel");
this->set_last_pos(*pt);
}
} }
return gcode; return gcode;
@ -2891,7 +2893,13 @@ std::string GCodeGenerator::_extrude(
const std::string_view description_bridge = path_attr.role.is_bridge() ? " (bridge)"sv : ""sv; const std::string_view description_bridge = path_attr.role.is_bridge() ? " (bridge)"sv : ""sv;
// go to first point of extrusion path // go to first point of extrusion path
if (!m_last_pos_defined || m_last_pos != path.front().point) { if (!m_last_pos_defined) {
const double z = this->m_last_layer_z + this->m_config.z_offset.value;
const std::string comment{"move to print after unknown position"};
gcode += this->retract_and_wipe();
gcode += this->m_writer.travel_to_xy(this->point_to_gcode(path.front().point), comment);
gcode += this->m_writer.get_travel_to_z_gcode(z, comment);
} else if ( m_last_pos != path.front().point) {
std::string comment = "move to first "; std::string comment = "move to first ";
comment += description; comment += description;
comment += description_bridge; comment += description_bridge;
@ -3127,86 +3135,217 @@ std::string GCodeGenerator::_extrude(
return gcode; return gcode;
} }
// This method accepts &point in print coordinates. Points3 generate_flat_travel(tcb::span<const Point> xy_path, const float elevation) {
std::string GCodeGenerator::travel_to(const Point &point, ExtrusionRole role, std::string comment) Points3 result;
{ result.reserve(xy_path.size() - 1);
/* Define the travel move as a line between current position and the taget point. for (const Point& point : xy_path.subspan(1)) {
This is expressed in print coordinates, so it will need to be translated by result.emplace_back(point.x(), point.y(), scaled(elevation));
this->origin in order to get G-code coordinates. */ }
Polyline travel { this->last_pos(), point }; return result;
}
if (this->config().avoid_crossing_curled_overhangs) { Vec2d place_at_segment(const Vec2d& current_point, const Vec2d& previous_point, const double distance) {
if (m_config.avoid_crossing_perimeters) { Vec2d direction = (current_point - previous_point).normalized();
BOOST_LOG_TRIVIAL(warning) return previous_point + direction * distance;
<< "Option >avoid crossing curled overhangs< is not compatible with avoid crossing perimeters and it will be ignored!"; }
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 { } else {
Point scaled_origin = Point(scaled(this->origin())); return this->params.lift_height;
travel = m_avoid_crossing_curled_overhangs.find_path(this->last_pos() + scaled_origin, point + scaled_origin);
travel.translate(-scaled_origin);
} }
} }
// check whether a straight travel move would need retraction ElevatedTravelParams params{};
bool needs_retraction = this->needs_retraction(travel, role); };
// check whether wipe could be disabled without causing visible stringing
bool could_be_wipe_disabled = false;
// Save state of use_external_mp_once for the case that will be needed to call twice m_avoid_crossing_perimeters.travel_to.
const bool used_external_mp_once = m_avoid_crossing_perimeters.used_external_mp_once();
// if a retraction would be needed, try to use avoid_crossing_perimeters to plan a Points3 generate_elevated_travel(
// multi-hop travel path inside the configuration space const tcb::span<const Point> xy_path,
if (needs_retraction const std::vector<double>& ensure_points_at_distances,
&& m_config.avoid_crossing_perimeters const double initial_elevation,
&& ! m_avoid_crossing_perimeters.disabled_once()) { const std::function<double(double)>& elevation
travel = m_avoid_crossing_perimeters.travel_to(*this, point, &could_be_wipe_disabled); ) {
// check again whether the new travel path still needs a retraction Points3 result{};
needs_retraction = this->needs_retraction(travel, role);
//if (needs_retraction && m_layer_index > 1) exit(0); 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)));
} }
// Re-allow avoid_crossing_perimeters for the next travel moves return result;
m_avoid_crossing_perimeters.reset_once_modifiers(); }
AABBTreeLines::LinesDistancer<Linef> get_expolygons_distancer(const ExPolygons& polygons) {
std::vector<Linef> lines;
for (const ExPolygon& polygon : polygons) {
for (const Line& line : polygon.lines()) {
lines.emplace_back(unscaled(line.a), unscaled(line.b));
}
}
return AABBTreeLines::LinesDistancer{std::move(lines)};
}
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 {};
}
ElevatedTravelParams get_elevated_traval_params(
const FullPrintConfig& config,
const unsigned extruder_id
)
{
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);
}
return elevation_params;
}
Points3 generate_travel_to_extrusion(
const Polyline& xy_path,
const FullPrintConfig& config,
const unsigned extruder_id,
const double initial_elevation
) {
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);
}
ElevatedTravelParams elevation_params{get_elevated_traval_params(
config,
extruder_id
)};
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(
const Points3& travel,
const std::string& comment
) {
std::string gcode;
const unsigned acceleration =(unsigned)(m_config.travel_acceleration.value + 0.5);
if (travel.empty()) {
return "";
}
// generate G-code for the travel move // generate G-code for the travel move
std::string gcode;
if (needs_retraction) {
if (m_config.avoid_crossing_perimeters && could_be_wipe_disabled)
m_wipe.reset_path();
Point last_post_before_retract = this->last_pos();
gcode += this->retract();
// When "Wipe while retracting" is enabled, then extruder moves to another position, and travel from this position can cross perimeters.
// Because of it, it is necessary to call avoid crossing perimeters again with new starting point after calling retraction()
// FIXME Lukas H.: Try to predict if this second calling of avoid crossing perimeters will be needed or not. It could save computations.
if (last_post_before_retract != this->last_pos() && m_config.avoid_crossing_perimeters) {
// If in the previous call of m_avoid_crossing_perimeters.travel_to was use_external_mp_once set to true restore this value for next call.
if (used_external_mp_once)
m_avoid_crossing_perimeters.use_external_mp_once();
travel = m_avoid_crossing_perimeters.travel_to(*this, point);
// If state of use_external_mp_once was changed reset it to right value.
if (used_external_mp_once)
m_avoid_crossing_perimeters.reset_once_modifiers();
}
} else
// Reset the wipe path when traveling, so one would not wipe along an old path.
m_wipe.reset_path();
// use G1 because we rely on paths being straight (G0 may make round paths) // use G1 because we rely on paths being straight (G0 may make round paths)
if (travel.size() >= 2) { gcode += this->m_writer.set_travel_acceleration(acceleration);
gcode += m_writer.set_travel_acceleration((unsigned int)(m_config.travel_acceleration.value + 0.5)); for (const Vec3crd& point : travel) {
gcode += this->m_writer.travel_to_xyz(to_3d(this->point_to_gcode(point.head<2>()), unscaled(point.z())), comment);
for (size_t i = 1; i < travel.size(); ++ i) this->set_last_pos(point.head<2>());
gcode += m_writer.travel_to_xy(this->point_to_gcode(travel.points[i]), comment);
if (! GCodeWriter::supports_separate_travel_acceleration(config().gcode_flavor)) {
// In case that this flavor does not support separate print and travel acceleration,
// reset acceleration to default.
gcode += m_writer.set_travel_acceleration((unsigned int)(m_config.travel_acceleration.value + 0.5));
}
this->set_last_pos(travel.points.back());
} }
if (! GCodeWriter::supports_separate_travel_acceleration(config().gcode_flavor)) {
// In case that this flavor does not support separate print and travel acceleration,
// reset acceleration to default.
gcode += this->m_writer.set_travel_acceleration(acceleration);
}
return gcode; return gcode;
} }
@ -3249,7 +3388,104 @@ bool GCodeGenerator::needs_retraction(const Polyline &travel, ExtrusionRole role
return true; return true;
} }
std::string GCodeGenerator::retract(bool toolchange) Polyline GCodeGenerator::generate_travel_xy_path(
const Point& start_point,
const Point& end_point,
const bool needs_retraction,
bool& could_be_wipe_disabled
) {
const Point scaled_origin{scaled(this->origin())};
const bool avoid_crossing_perimeters = (
this->m_config.avoid_crossing_perimeters
&& !this->m_avoid_crossing_perimeters.disabled_once()
);
Polyline xy_path{start_point, end_point};
if (m_config.avoid_crossing_curled_overhangs) {
if (avoid_crossing_perimeters) {
BOOST_LOG_TRIVIAL(warning)
<< "Option >avoid crossing curled overhangs< is not compatible with avoid crossing perimeters and it will be ignored!";
} else {
xy_path = this->m_avoid_crossing_curled_overhangs.find_path(
start_point + scaled_origin,
end_point + scaled_origin
);
xy_path.translate(-scaled_origin);
}
}
// if a retraction would be needed, try to use avoid_crossing_perimeters to plan a
// multi-hop travel path inside the configuration space
if (
needs_retraction
&& avoid_crossing_perimeters
) {
xy_path = this->m_avoid_crossing_perimeters.travel_to(*this, end_point, &could_be_wipe_disabled);
}
return xy_path;
}
// This method accepts &point in print coordinates.
std::string GCodeGenerator::travel_to(const Point &point, ExtrusionRole role, std::string comment)
{
const Point start_point = this->last_pos();
using namespace GCode::Impl;
// check whether a straight travel move would need retraction
bool could_be_wipe_disabled {false};
bool needs_retraction = this->needs_retraction(Polyline{start_point, point}, role);
Polyline xy_path{generate_travel_xy_path(
start_point, point, needs_retraction, could_be_wipe_disabled
)};
needs_retraction = this->needs_retraction(xy_path, role);
std::string wipe_retract_gcode{};
if (needs_retraction) {
if (could_be_wipe_disabled) {
m_wipe.reset_path();
}
Point position_before_wipe{this->last_pos()};
wipe_retract_gcode = this->retract_and_wipe();
if (this->last_pos() != position_before_wipe) {
xy_path = generate_travel_xy_path(
this->last_pos(), point, needs_retraction, could_be_wipe_disabled
);
}
} else {
m_wipe.reset_path();
}
this->m_avoid_crossing_perimeters.reset_once_modifiers();
const unsigned extruder_id = this->m_writer.extruder()->id();
const double retract_length = this->m_config.retract_length.get_at(extruder_id);
bool can_be_flat{!needs_retraction || retract_length == 0};
const double initial_elevation = this->m_last_layer_z + this->m_config.z_offset.value;
const Points3 travel = (
can_be_flat ?
generate_flat_travel(xy_path.points, initial_elevation) :
GCode::Impl::generate_travel_to_extrusion(
xy_path,
this->m_config,
extruder_id,
initial_elevation
)
);
return wipe_retract_gcode + generate_travel_gcode(travel, comment);
}
std::string GCodeGenerator::retract_and_wipe(bool toolchange)
{ {
std::string gcode; std::string gcode;
@ -3267,10 +3503,7 @@ std::string GCodeGenerator::retract(bool toolchange)
methods even if we performed wipe, since this will ensure the entire retraction methods even if we performed wipe, since this will ensure the entire retraction
length is honored in case wipe path was too short. */ length is honored in case wipe path was too short. */
gcode += toolchange ? m_writer.retract_for_toolchange() : m_writer.retract(); gcode += toolchange ? m_writer.retract_for_toolchange() : m_writer.retract();
gcode += m_writer.reset_e(); gcode += m_writer.reset_e();
if (m_writer.extruder()->retract_length() > 0 || m_config.use_firmware_retraction)
gcode += m_writer.lift();
return gcode; return gcode;
} }
@ -3302,7 +3535,7 @@ std::string GCodeGenerator::set_extruder(unsigned int extruder_id, double print_
} }
// prepend retraction on the current extruder // prepend retraction on the current extruder
std::string gcode = this->retract(true); std::string gcode = this->retract_and_wipe(true);
// Always reset the extrusion path, even if the tool change retract is set to zero. // Always reset the extrusion path, even if the tool change retract is set to zero.
m_wipe.reset_path(); m_wipe.reset_path();
@ -3378,6 +3611,9 @@ std::string GCodeGenerator::set_extruder(unsigned int extruder_id, double print_
if (m_ooze_prevention.enable) if (m_ooze_prevention.enable)
gcode += m_ooze_prevention.post_toolchange(*this); gcode += m_ooze_prevention.post_toolchange(*this);
// The position is now known after the tool change.
this->m_last_pos_defined = false;
return gcode; return gcode;
} }

View File

@ -40,6 +40,7 @@
#include "GCode/GCodeProcessor.hpp" #include "GCode/GCodeProcessor.hpp"
#include "EdgeGrid.hpp" #include "EdgeGrid.hpp"
#include "GCode/ThumbnailData.hpp" #include "GCode/ThumbnailData.hpp"
#include "tcbspan/span.hpp"
#include <memory> #include <memory>
#include <map> #include <map>
@ -88,6 +89,53 @@ struct LayerResult {
static LayerResult make_nop_layer_result() { return {"", std::numeric_limits<coord_t>::max(), false, false, true}; } static LayerResult make_nop_layer_result() { return {"", std::numeric_limits<coord_t>::max(), false, false, true}; }
}; };
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
);
}
class GCodeGenerator { class GCodeGenerator {
public: public:
@ -303,11 +351,21 @@ private:
const bool print_wipe_extrusions); const bool print_wipe_extrusions);
std::string extrude_support(const ExtrusionEntityReferences &support_fills, const GCode::SmoothPathCache &smooth_path_cache); std::string extrude_support(const ExtrusionEntityReferences &support_fills, const GCode::SmoothPathCache &smooth_path_cache);
std::string generate_travel_gcode(
const Points3& travel,
const std::string& comment
);
Polyline generate_travel_xy_path(
const Point& start,
const Point& end,
const bool needs_retraction,
bool& could_be_wipe_disabled
);
std::string travel_to(const Point &point, ExtrusionRole role, std::string comment); std::string travel_to(const Point &point, ExtrusionRole role, std::string comment);
bool needs_retraction(const Polyline &travel, ExtrusionRole role = ExtrusionRole::None); bool needs_retraction(const Polyline &travel, ExtrusionRole role = ExtrusionRole::None);
std::string retract(bool toolchange = false);
std::string unretract() { return m_writer.unlift() + m_writer.unretract(); } std::string retract_and_wipe(bool toolchange = false);
std::string unretract() { return m_writer.unretract(); }
std::string set_extruder(unsigned int extruder_id, double print_z); std::string set_extruder(unsigned int extruder_id, double print_z);
// Cache for custom seam enforcers/blockers for each layer. // Cache for custom seam enforcers/blockers for each layer.
@ -336,8 +394,8 @@ private:
// Input/output from/to custom G-code block, for returning position, retraction etc. // Input/output from/to custom G-code block, for returning position, retraction etc.
DynamicConfig output_config; DynamicConfig output_config;
ConfigOptionFloats *opt_position { nullptr }; ConfigOptionFloats *opt_position { nullptr };
ConfigOptionFloat *opt_zhop { nullptr };
ConfigOptionFloats *opt_e_position { nullptr }; ConfigOptionFloats *opt_e_position { nullptr };
ConfigOptionFloat *opt_zhop { nullptr };
ConfigOptionFloats *opt_e_retracted { nullptr }; ConfigOptionFloats *opt_e_retracted { nullptr };
ConfigOptionFloats *opt_e_restart_extra { nullptr }; ConfigOptionFloats *opt_e_restart_extra { nullptr };
ConfigOptionFloats *opt_extruded_volume { nullptr }; ConfigOptionFloats *opt_extruded_volume { nullptr };

View File

@ -742,7 +742,7 @@ static bool need_wipe(const GCodeGenerator &gcodegen,
const Polyline &result_travel, const Polyline &result_travel,
const size_t intersection_count) const size_t intersection_count)
{ {
bool z_lift_enabled = gcodegen.config().retract_lift.get_at(gcodegen.writer().extruder()->id()) > 0.; bool z_lift_enabled = gcodegen.config().travel_max_lift.get_at(gcodegen.writer().extruder()->id()) > 0.;
bool wipe_needed = false; bool wipe_needed = false;
// If the original unmodified path doesn't have any intersection with boundary, then it is entirely inside the object otherwise is entirely // If the original unmodified path doesn't have any intersection with boundary, then it is entirely inside the object otherwise is entirely

View File

@ -20,6 +20,7 @@
#include <map> #include <map>
#include <assert.h> #include <assert.h>
#include <string_view> #include <string_view>
#include <boost/math/special_functions/pow.hpp>
#ifdef __APPLE__ #ifdef __APPLE__
#include <boost/spirit/include/karma.hpp> #include <boost/spirit/include/karma.hpp>
@ -304,57 +305,30 @@ std::string GCodeWriter::travel_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij
std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string_view comment) std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string_view comment)
{ {
// FIXME: This function was not being used when travel_speed_z was separated (bd6badf). if (std::abs(point.x() - m_pos.x()) < EPSILON && std::abs(point.y() - m_pos.y()) < EPSILON) {
// Calculation of feedrate was not updated accordingly. If you want to use return this->travel_to_z(point.z(), comment);
// this function, fix it first. } else if (std::abs(point.z() - m_pos.z()) < EPSILON) {
std::terminate(); return this->travel_to_xy(point.head<2>(), comment);
} else {
m_pos = point;
/* If target Z is lower than current Z but higher than nominal Z we GCodeG1Formatter w;
don't perform the Z move but we only move in the XY plane and w.emit_xyz(point);
adjust the nominal Z by reducing the lift amount that will be
used for unlift. */ Vec2f speed {this->config.travel_speed_z.value, this->config.travel_speed.value};
if (!this->will_move_z(point.z())) { w.emit_f(speed.norm() * 60.0);
double nominal_z = m_pos.z() - m_lifted; w.emit_comment(this->config.gcode_comments, comment);
m_lifted -= (point.z() - nominal_z); return w.string();
// In case that retract_lift == layer_height we could end up with almost zero in_m_lifted
// and a retract could be skipped (https://github.com/prusa3d/PrusaSlicer/issues/2154
if (std::abs(m_lifted) < EPSILON)
m_lifted = 0.;
return this->travel_to_xy(to_2d(point));
} }
/* In all the other cases, we perform an actual XYZ move and cancel
the lift. */
m_lifted = 0;
m_pos = point;
GCodeG1Formatter w;
w.emit_xyz(point);
w.emit_f(this->config.travel_speed.value * 60.0);
w.emit_comment(this->config.gcode_comments, comment);
return w.string();
} }
std::string GCodeWriter::travel_to_z(double z, const std::string_view comment) std::string GCodeWriter::travel_to_z(double z, const std::string_view comment)
{ {
/* If target Z is lower than current Z but higher than nominal Z return std::abs(m_pos.z() - z) < EPSILON ? "" : this->get_travel_to_z_gcode(z, comment);
we don't perform the move but we only adjust the nominal Z by
reducing the lift amount that will be used for unlift. */
if (!this->will_move_z(z)) {
double nominal_z = m_pos.z() - m_lifted;
m_lifted -= (z - nominal_z);
if (std::abs(m_lifted) < EPSILON)
m_lifted = 0.;
return {};
}
/* In all the other cases, we perform an actual Z move and cancel
the lift. */
m_lifted = 0;
return this->_travel_to_z(z, comment);
} }
std::string GCodeWriter::_travel_to_z(double z, const std::string_view comment) std::string GCodeWriter::get_travel_to_z_gcode(double z, const std::string_view comment)
{ {
m_pos.z() = z; m_pos.z() = z;
@ -369,18 +343,6 @@ std::string GCodeWriter::_travel_to_z(double z, const std::string_view comment)
return w.string(); return w.string();
} }
bool GCodeWriter::will_move_z(double z) const
{
/* If target Z is lower than current Z but higher than nominal Z
we don't perform an actual Z move. */
if (m_lifted > 0) {
double nominal_z = m_pos.z() - m_lifted;
if (z >= nominal_z && z <= m_pos.z())
return false;
}
return true;
}
std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std::string_view comment) std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std::string_view comment)
{ {
assert(dE != 0); assert(dE != 0);
@ -514,47 +476,8 @@ std::string GCodeWriter::unretract()
return gcode; return gcode;
} }
/* If this method is called more than once before calling unlift(),
it will not perform subsequent lifts, even if Z was raised manually
(i.e. with travel_to_z()) and thus _lifted was reduced. */
std::string GCodeWriter::lift()
{
// check whether the above/below conditions are met
double target_lift = 0;
{
double above = this->config.retract_lift_above.get_at(m_extruder->id());
double below = this->config.retract_lift_below.get_at(m_extruder->id());
if (m_pos.z() >= above && (below == 0 || m_pos.z() <= below))
target_lift = this->config.retract_lift.get_at(m_extruder->id());
}
if (m_lifted == 0 && target_lift > 0) {
m_lifted = target_lift;
return this->_travel_to_z(m_pos.z() + target_lift, "lift Z");
}
return {};
}
std::string GCodeWriter::unlift()
{
std::string gcode;
if (m_lifted > 0) {
gcode += this->_travel_to_z(m_pos.z() - m_lifted, "restore layer Z");
m_lifted = 0;
}
return gcode;
}
void GCodeWriter::update_position(const Vec3d &new_pos) void GCodeWriter::update_position(const Vec3d &new_pos)
{ {
assert(this->m_lifted >= 0);
const double nominal_z = m_pos.z() - m_lifted;
m_lifted = new_pos.z() - nominal_z;
if (m_lifted < - EPSILON)
throw Slic3r::RuntimeError("Custom G-code reports negative Z-hop. Final Z position is below the print_z height.");
// In case that retract_lift == layer_height we could end up with almost zero in_m_lifted
// and a retract could be skipped (https://github.com/prusa3d/PrusaSlicer/issues/2154
if (m_lifted < EPSILON)
m_lifted = 0.;
m_pos = new_pos; m_pos = new_pos;
} }

View File

@ -30,8 +30,7 @@ public:
multiple_extruders(false), m_extrusion_axis("E"), m_extruder(nullptr), multiple_extruders(false), m_extrusion_axis("E"), m_extruder(nullptr),
m_single_extruder_multi_material(false), m_single_extruder_multi_material(false),
m_last_acceleration(0), m_max_acceleration(0), m_last_acceleration(0), m_max_acceleration(0),
m_last_bed_temperature(0), m_last_bed_temperature_reached(true), m_last_bed_temperature(0), m_last_bed_temperature_reached(true)
m_lifted(0)
{} {}
Extruder* extruder() { return m_extruder; } Extruder* extruder() { return m_extruder; }
const Extruder* extruder() const { return m_extruder; } const Extruder* extruder() const { return m_extruder; }
@ -70,23 +69,21 @@ public:
std::string travel_to_xy(const Vec2d &point, const std::string_view comment = {}); std::string travel_to_xy(const Vec2d &point, const std::string_view comment = {});
std::string travel_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, const std::string_view comment = {}); std::string travel_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, const std::string_view comment = {});
std::string travel_to_xyz(const Vec3d &point, const std::string_view comment = {}); std::string travel_to_xyz(const Vec3d &point, const std::string_view comment = {});
std::string get_travel_to_z_gcode(double z, const std::string_view comment);
std::string travel_to_z(double z, const std::string_view comment = {}); std::string travel_to_z(double z, const std::string_view comment = {});
bool will_move_z(double z) const;
std::string extrude_to_xy(const Vec2d &point, double dE, const std::string_view comment = {}); std::string extrude_to_xy(const Vec2d &point, double dE, const std::string_view comment = {});
std::string extrude_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, double dE, const std::string_view comment); std::string extrude_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, double dE, const std::string_view comment);
// std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string_view comment = {}); // std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string_view comment = {});
std::string retract(bool before_wipe = false); std::string retract(bool before_wipe = false);
std::string retract_for_toolchange(bool before_wipe = false); std::string retract_for_toolchange(bool before_wipe = false);
std::string unretract(); std::string unretract();
std::string lift();
std::string unlift();
// Current position of the printer, in G-code coordinates. // Current position of the printer, in G-code coordinates.
// Z coordinate of current position contains zhop. If zhop is applied (this->zhop() > 0), // Z coordinate of current position contains zhop. If zhop is applied (this->zhop() > 0),
// then the print_z = this->get_position().z() - this->zhop(). // then the print_z = this->get_position().z() - this->zhop().
Vec3d get_position() const { return m_pos; } Vec3d get_position() const { return m_pos; }
// Current Z hop value. // Zhop value is obsolete. This is for backwards compability.
double get_zhop() const { return m_lifted; } double get_zhop() const { return 0; }
// Update position of the print head based on the final position returned by a custom G-code block. // Update position of the print head based on the final position returned by a custom G-code block.
// The new position Z coordinate contains the Z-hop. // The new position Z coordinate contains the Z-hop.
// GCodeWriter expects the custom script to NOT change print_z, only Z-hop, thus the print_z is maintained // GCodeWriter expects the custom script to NOT change print_z, only Z-hop, thus the print_z is maintained
@ -117,7 +114,6 @@ private:
unsigned int m_last_bed_temperature; unsigned int m_last_bed_temperature;
bool m_last_bed_temperature_reached; bool m_last_bed_temperature_reached;
double m_lifted;
Vec3d m_pos = Vec3d::Zero(); Vec3d m_pos = Vec3d::Zero();
enum class Acceleration { enum class Acceleration {
@ -125,7 +121,6 @@ private:
Print Print
}; };
std::string _travel_to_z(double z, const std::string_view comment);
std::string _retract(double length, double restart_extra, const std::string_view comment); std::string _retract(double length, double restart_extra, const std::string_view comment);
std::string set_acceleration_internal(Acceleration type, unsigned int acceleration); std::string set_acceleration_internal(Acceleration type, unsigned int acceleration);
}; };

View File

@ -537,6 +537,7 @@ WipeTower::WipeTower(const PrintConfig& config, const PrintRegionConfig& default
m_no_sparse_layers(config.wipe_tower_no_sparse_layers), m_no_sparse_layers(config.wipe_tower_no_sparse_layers),
m_gcode_flavor(config.gcode_flavor), m_gcode_flavor(config.gcode_flavor),
m_travel_speed(config.travel_speed), m_travel_speed(config.travel_speed),
m_travel_speed_z(config.travel_speed_z),
m_infill_speed(default_region_config.infill_speed), m_infill_speed(default_region_config.infill_speed),
m_perimeter_speed(default_region_config.perimeter_speed), m_perimeter_speed(default_region_config.perimeter_speed),
m_current_tool(initial_tool), m_current_tool(initial_tool),
@ -1018,7 +1019,11 @@ void WipeTower::toolchange_Change(
writer.feedrate(m_travel_speed * 60.f) // see https://github.com/prusa3d/PrusaSlicer/issues/5483 writer.feedrate(m_travel_speed * 60.f) // see https://github.com/prusa3d/PrusaSlicer/issues/5483
.append(std::string("G1 X") + Slic3r::float_to_string_decimal_point(current_pos.x()) .append(std::string("G1 X") + Slic3r::float_to_string_decimal_point(current_pos.x())
+ " Y" + Slic3r::float_to_string_decimal_point(current_pos.y()) + " Y" + Slic3r::float_to_string_decimal_point(current_pos.y())
+ never_skip_tag() + "\n"); + never_skip_tag() + "\n"
);
writer.feedrate(m_travel_speed_z * 60.f)
.append("G1 Z" + Slic3r::float_to_string_decimal_point(this->m_z_pos) + "\n");
writer.append("[deretraction_from_wipe_tower_generator]"); writer.append("[deretraction_from_wipe_tower_generator]");
// The toolchange Tn command will be inserted later, only in case that the user does // The toolchange Tn command will be inserted later, only in case that the user does

View File

@ -279,6 +279,7 @@ private:
size_t m_max_color_changes = 0; // Maximum number of color changes per layer. size_t m_max_color_changes = 0; // Maximum number of color changes per layer.
int m_old_temperature = -1; // To keep track of what was the last temp that we set (so we don't issue the command when not neccessary) int m_old_temperature = -1; // To keep track of what was the last temp that we set (so we don't issue the command when not neccessary)
float m_travel_speed = 0.f; float m_travel_speed = 0.f;
float m_travel_speed_z = 0.f;
float m_infill_speed = 0.f; float m_infill_speed = 0.f;
float m_perimeter_speed = 0.f; float m_perimeter_speed = 0.f;
float m_first_layer_speed = 0.f; float m_first_layer_speed = 0.f;

View File

@ -41,9 +41,9 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip
std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation); std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation);
gcode += gcodegen.writer().unlift(); // Make sure there is no z-hop (in most cases, there isn't).
double current_z = gcodegen.writer().get_position().z(); double current_z = gcodegen.writer().get_position().z();
gcode += gcodegen.writer().travel_to_z(current_z);
if (z == -1.) // in case no specific z was provided, print at current_z pos if (z == -1.) // in case no specific z was provided, print at current_z pos
z = current_z; z = current_z;
@ -57,7 +57,7 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip
|| is_ramming || is_ramming
|| will_go_down); // don't dig into the print || will_go_down); // don't dig into the print
if (should_travel_to_tower) { if (should_travel_to_tower) {
gcode += gcodegen.retract(); gcode += gcodegen.retract_and_wipe();
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once(); gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
gcode += gcodegen.travel_to( gcode += gcodegen.travel_to(
wipe_tower_point_to_object_point(gcodegen, start_pos), wipe_tower_point_to_object_point(gcodegen, start_pos),
@ -150,12 +150,15 @@ std::string WipeTowerIntegration::post_process_wipe_tower_moves(const WipeTower:
} }
std::ostringstream line_out; std::ostringstream line_out;
std::istringstream line_str(line); std::istringstream line_str(line);
std::optional<float> z{};
line_str >> std::noskipws; // don't skip whitespace line_str >> std::noskipws; // don't skip whitespace
char ch = 0; char ch = 0;
line_str >> ch >> ch; // read the "G1" line_str >> ch >> ch; // read the "G1"
while (line_str >> ch) { while (line_str >> ch) {
if (ch == 'X' || ch == 'Y') if (ch == 'X' || ch == 'Y')
line_str >> (ch == 'X' ? pos.x() : pos.y()); line_str >> (ch == 'X' ? pos.x() : pos.y());
else if (ch == 'Z')
line_str >> *z;
else else
line_out << ch; line_out << ch;
} }
@ -171,6 +174,8 @@ std::string WipeTowerIntegration::post_process_wipe_tower_moves(const WipeTower:
oss << " X" << transformed_pos.x() - extruder_offset.x(); oss << " X" << transformed_pos.x() - extruder_offset.x();
if (transformed_pos.y() != old_pos.y() || never_skip) if (transformed_pos.y() != old_pos.y() || never_skip)
oss << " Y" << transformed_pos.y() - extruder_offset.y(); oss << " Y" << transformed_pos.y() - extruder_offset.y();
if (z)
oss << " Z" << *z;
if (! line.empty()) if (! line.empty())
oss << " "; oss << " ";
line = oss.str() + line; line = oss.str() + line;
@ -243,7 +248,10 @@ std::string WipeTowerIntegration::finalize(GCodeGenerator &gcodegen)
{ {
std::string gcode; std::string gcode;
if (std::abs(gcodegen.writer().get_position().z() - m_final_purge.print_z) > EPSILON) if (std::abs(gcodegen.writer().get_position().z() - m_final_purge.print_z) > EPSILON)
gcode += gcodegen.change_layer(m_final_purge.print_z); gcode += gcodegen.generate_travel_gcode(
{{gcodegen.last_pos().x(), gcodegen.last_pos().y(), scaled(m_final_purge.print_z)}},
"move to safe place for purging"
);
gcode += append_tcr(gcodegen, m_final_purge, -1); gcode += append_tcr(gcodegen, m_final_purge, -1);
return gcode; return gcode;
} }

View File

@ -24,7 +24,7 @@ use Slic3r::Test qw(_eq);
my $tool = 0; my $tool = 0;
my @toolchange_count = (); # track first usages so that we don't expect retract_length_toolchange when extruders are used for the first time my @toolchange_count = (); # track first usages so that we don't expect retract_length_toolchange when extruders are used for the first time
my @retracted = (1); # ignore the first travel move from home to first point my @retracted = (0);
my @retracted_length = (0); my @retracted_length = (0);
my $lifted = 0; my $lifted = 0;
my $lift_dist = 0; # track lifted distance for toolchanges and extruders with different retract_lift values my $lift_dist = 0; # track lifted distance for toolchanges and extruders with different retract_lift values
@ -155,7 +155,7 @@ use Slic3r::Test qw(_eq);
if ($info->{dist_Z} && $retracted) { if ($info->{dist_Z} && $retracted) {
$layer_changes_with_retraction++; $layer_changes_with_retraction++;
} }
if ($info->{dist_Z} && $args->{Z} < $self->Z) { if ($info->{dist_Z} && $args->{Z} < $self->{Z}) {
$z_restores++; $z_restores++;
} }
}); });

View File

@ -5,6 +5,7 @@
#include "libslic3r/GCode.hpp" #include "libslic3r/GCode.hpp"
using namespace Slic3r; using namespace Slic3r;
using namespace Slic3r::GCode::Impl;
SCENARIO("Origin manipulation", "[GCode]") { SCENARIO("Origin manipulation", "[GCode]") {
Slic3r::GCodeGenerator gcodegen; Slic3r::GCodeGenerator gcodegen;
@ -20,3 +21,121 @@ 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})
});
}

View File

@ -6,68 +6,6 @@
using namespace Slic3r; using namespace Slic3r;
SCENARIO("lift() is not ignored after unlift() at normal values of Z", "[GCodeWriter]") {
GIVEN("A config from a file and a single extruder.") {
GCodeWriter writer;
GCodeConfig &config = writer.config;
config.load(std::string(TEST_DATA_DIR) + "/fff_print_tests/test_gcodewriter/config_lift_unlift.ini", ForwardCompatibilitySubstitutionRule::Disable);
std::vector<unsigned int> extruder_ids {0};
writer.set_extruders(extruder_ids);
writer.set_extruder(0);
WHEN("Z is set to 203") {
double trouble_Z = 203;
writer.travel_to_z(trouble_Z);
AND_WHEN("GcodeWriter::Lift() is called") {
REQUIRE(writer.lift().size() > 0);
AND_WHEN("Z is moved post-lift to the same delta as the config Z lift") {
REQUIRE(writer.travel_to_z(trouble_Z + config.retract_lift.values[0]).size() == 0);
AND_WHEN("GCodeWriter::Unlift() is called") {
REQUIRE(writer.unlift().size() == 0); // we're the same height so no additional move happens.
THEN("GCodeWriter::Lift() emits gcode.") {
REQUIRE(writer.lift().size() > 0);
}
}
}
}
}
WHEN("Z is set to 500003") {
double trouble_Z = 500003;
writer.travel_to_z(trouble_Z);
AND_WHEN("GcodeWriter::Lift() is called") {
REQUIRE(writer.lift().size() > 0);
AND_WHEN("Z is moved post-lift to the same delta as the config Z lift") {
REQUIRE(writer.travel_to_z(trouble_Z + config.retract_lift.values[0]).size() == 0);
AND_WHEN("GCodeWriter::Unlift() is called") {
REQUIRE(writer.unlift().size() == 0); // we're the same height so no additional move happens.
THEN("GCodeWriter::Lift() emits gcode.") {
REQUIRE(writer.lift().size() > 0);
}
}
}
}
}
WHEN("Z is set to 10.3") {
double trouble_Z = 10.3;
writer.travel_to_z(trouble_Z);
AND_WHEN("GcodeWriter::Lift() is called") {
REQUIRE(writer.lift().size() > 0);
AND_WHEN("Z is moved post-lift to the same delta as the config Z lift") {
REQUIRE(writer.travel_to_z(trouble_Z + config.retract_lift.values[0]).size() == 0);
AND_WHEN("GCodeWriter::Unlift() is called") {
REQUIRE(writer.unlift().size() == 0); // we're the same height so no additional move happens.
THEN("GCodeWriter::Lift() emits gcode.") {
REQUIRE(writer.lift().size() > 0);
}
}
}
}
}
// The test above will fail for trouble_Z == 9007199254740992, where trouble_Z + 1.5 will be rounded to trouble_Z + 2.0 due to double mantisa overflow.
}
}
SCENARIO("set_speed emits values with fixed-point output.", "[GCodeWriter]") { SCENARIO("set_speed emits values with fixed-point output.", "[GCodeWriter]") {
GIVEN("GCodeWriter instance") { GIVEN("GCodeWriter instance") {