Merge branch 'jalapeno_z_skip' (PR #14060 by @jalapenopuzzle, also related to SPE-2675)

This commit is contained in:
Lukas Matena 2025-02-12 17:05:35 +01:00
commit 9b8790e4d8
7 changed files with 675 additions and 69 deletions

View File

@ -1277,7 +1277,7 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
file.write(this->retract_and_wipe());
file.write(m_label_objects.maybe_stop_instance());
const double last_z{this->writer().get_position().z()};
file.write(this->writer().get_travel_to_z_gcode(last_z, "ensure z position"));
file.write(this->writer().travel_to_z_force(last_z, "ensure z position"));
const Vec3crd from{to_3d(*this->last_position, scaled(this->m_last_layer_z))};
const Vec3crd to{0, 0, scaled(this->m_last_layer_z)};
file.write(this->travel_to(from, to, ExtrusionRole::None, "move to origin position for next object", [](){return "";}));
@ -2253,7 +2253,7 @@ std::string GCodeGenerator::generate_ramping_layer_change_gcode(
const Polyline &xy_path,
const double initial_elevation,
const GCode::Impl::Travels::ElevatedTravelParams &elevation_params
) const {
) {
using namespace GCode::Impl::Travels;
const std::vector<double> ensure_points_at_distances = linspace(
@ -2271,7 +2271,7 @@ std::string GCodeGenerator::generate_ramping_layer_change_gcode(
for (const Vec3crd &point : travel) {
const Vec3d gcode_point{this->point_to_gcode(point)};
travel_gcode += this->m_writer
.get_travel_to_xyz_gcode(gcode_point, "layer change");
.travel_to_xyz_force(gcode_point, "layer change");
}
return travel_gcode;
}
@ -2992,11 +2992,7 @@ std::string GCodeGenerator::change_layer(
this->last_position = this->gcode_to_point(unscaled(first_point));
} else {
if (!first_layer) {
// travel_to_z is not used as it may not generate the travel if the writter z == print_z.
gcode += this->writer().get_travel_to_z_gcode(print_z, "simple layer change");
Vec3d position{this->writer().get_position()};
position.z() = print_z;
this->writer().update_position(position);
gcode += this->writer().travel_to_z_force(print_z, "simple layer change");
} else {
Vec3d position{this->writer().get_position()};
position.z() = position.z() + m_config.z_offset;
@ -3239,7 +3235,7 @@ std::string GCodeGenerator::travel_to_first_position(const Vec3crd& point, const
if (!EXTRUDER_CONFIG(travel_ramping_lift) && this->last_position) {
const Vec3crd from{to_3d(*this->last_position, scaled(from_z))};
gcode = this->travel_to(
from, point, role, "travel to first layer point", insert_gcode
from, point, role, "travel to first layer point", insert_gcode, EnforceFirstZ::True
);
} else {
double lift{
@ -3255,15 +3251,15 @@ std::string GCodeGenerator::travel_to_first_position(const Vec3crd& point, const
if (EXTRUDER_CONFIG(retract_length) > 0 && !this->last_position) {
if (!this->last_position || EXTRUDER_CONFIG(retract_before_travel) < (this->point_to_gcode(*this->last_position) - gcode_point.head<2>()).norm()) {
gcode += this->writer().retract();
gcode += this->writer().get_travel_to_z_gcode(from_z + lift, "lift");
gcode += this->writer().travel_to_z_force(from_z + lift, "lift");
}
}
const std::string comment{"move to first layer point"};
gcode += insert_gcode();
gcode += this->writer().get_travel_to_xy_gcode(gcode_point.head<2>(), comment);
gcode += this->writer().get_travel_to_z_gcode(gcode_point.z(), comment);
gcode += this->writer().travel_to_xy_force(gcode_point.head<2>(), comment);
gcode += this->writer().travel_to_z_force(gcode_point.z(), comment);
this->m_avoid_crossing_perimeters.reset_once_modifiers();
this->last_position = point.head<2>();
@ -3319,7 +3315,7 @@ std::string GCodeGenerator::_extrude(
gcode += this->retract_and_wipe();
gcode += m_writer.multiple_extruders ? "" : m_label_objects.maybe_change_instance(m_writer);
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);
gcode += this->m_writer.travel_to_z_force(z, comment);
} else if ( this->last_position != path.front().point) {
std::string comment = "move to first ";
comment += description;
@ -3586,7 +3582,8 @@ std::string GCodeGenerator::_extrude(
std::string GCodeGenerator::generate_travel_gcode(
const Points3& travel,
const std::string& comment,
const std::function<std::string()>& insert_gcode
const std::function<std::string()>& insert_gcode,
const EnforceFirstZ enforce_first_z
) {
std::string gcode;
@ -3610,7 +3607,18 @@ std::string GCodeGenerator::generate_travel_gcode(
already_inserted = true;
}
if (enforce_first_z == EnforceFirstZ::True && i == 0) {
if (
std::abs(gcode_point.x() - m_writer.get_position().x()) < GCodeFormatter::XYZ_EPSILON
&& std::abs(gcode_point.y() - m_writer.get_position().y()) < GCodeFormatter::XYZ_EPSILON
) {
gcode += this->m_writer.travel_to_z_force(gcode_point.z(), comment);
} else {
gcode += this->m_writer.travel_to_xyz_force(gcode_point, comment);
}
} else {
gcode += this->m_writer.travel_to_xyz(gcode_point, comment);
}
this->last_position = point.head<2>();
}
@ -3708,7 +3716,8 @@ std::string GCodeGenerator::travel_to(
const Vec3crd &end_point,
ExtrusionRole role,
const std::string &comment,
const std::function<std::string()>& insert_gcode
const std::function<std::string()>& insert_gcode,
const GCodeGenerator::EnforceFirstZ enforce_first_z
) {
const double initial_elevation{unscaled(start_point.z())};
@ -3775,7 +3784,7 @@ std::string GCodeGenerator::travel_to(
}
travel.emplace_back(end_point);
return wipe_retract_gcode + generate_travel_gcode(travel, comment, insert_gcode);
return wipe_retract_gcode + generate_travel_gcode(travel, comment, insert_gcode, enforce_first_z);
}
std::string GCodeGenerator::retract_and_wipe(bool toolchange, bool reset_e)

View File

@ -229,7 +229,7 @@ private:
const Polyline &xy_path,
const double initial_elevation,
const GCode::Impl::Travels::ElevatedTravelParams &elevation_params
) const;
);
std::vector<GCode::ExtrusionOrder::ExtruderExtrusions> get_sorted_extrusions(
const Print &print,
@ -329,10 +329,16 @@ private:
const std::vector<GCode::ExtrusionOrder::SupportPath> &support_extrusions
);
enum class EnforceFirstZ {
False,
True
};
std::string generate_travel_gcode(
const Points3& travel,
const std::string& comment,
const std::function<std::string()>& insert_gcode
const std::function<std::string()>& insert_gcode,
const EnforceFirstZ enforce_first_z = EnforceFirstZ::False
);
Polyline generate_travel_xy_path(
const Point& start,
@ -340,12 +346,14 @@ private:
const bool needs_retraction,
bool& could_be_wipe_disabled
);
std::string travel_to(
const Vec3crd &start_point,
const Vec3crd &end_point,
ExtrusionRole role,
const std::string &comment,
const std::function<std::string()>& insert_gcode
const std::function<std::string()>& insert_gcode,
const EnforceFirstZ enforce_first_z = EnforceFirstZ::False
);
std::string travel_to_first_position(const Vec3crd& point, const double from_z, const ExtrusionRole role, const std::function<std::string()>& insert_gcode);

View File

@ -295,19 +295,25 @@ std::string GCodeWriter::set_speed(double F, const std::string_view comment, con
return w.string();
}
std::string GCodeWriter::get_travel_to_xy_gcode(const Vec2d &point, const std::string_view comment) const
std::string GCodeWriter::travel_to_xy_force(const Vec2d &point, const std::string_view comment)
{
GCodeG1Formatter w;
w.emit_xy(point);
w.emit_f(this->config.travel_speed.value * 60.0);
w.emit_comment(this->config.gcode_comments, comment);
m_pos.head<2>() = point.head<2>();
return w.string();
}
std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string_view comment)
{
m_pos.head<2>() = point.head<2>();
return this->get_travel_to_xy_gcode(point, comment);
if (std::abs(point.x() - m_pos.x()) < GCodeFormatter::XYZ_EPSILON
&& std::abs(point.y() - m_pos.y()) < GCodeFormatter::XYZ_EPSILON)
{
return "";
} else {
return this->travel_to_xy_force(point, comment);
}
}
std::string GCodeWriter::travel_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, const std::string_view comment)
@ -329,51 +335,60 @@ std::string GCodeWriter::travel_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij
std::string GCodeWriter::travel_to_xyz(const Vec3d &to, const std::string_view comment)
{
if (std::abs(to.x() - m_pos.x()) < EPSILON && std::abs(to.y() - m_pos.y()) < EPSILON) {
return this->travel_to_z(to.z(), comment);
} else if (std::abs(to.z() - m_pos.z()) < EPSILON) {
return this->travel_to_xy(to.head<2>(), comment);
if (std::abs(to.x() - m_pos.x()) < GCodeFormatter::XYZ_EPSILON
&& std::abs(to.y() - m_pos.y()) < GCodeFormatter::XYZ_EPSILON
&& std::abs(to.z() - m_pos.z()) < GCodeFormatter::XYZ_EPSILON)
{
return "";
} else if (
std::abs(to.x() - m_pos.x()) < GCodeFormatter::XYZ_EPSILON
&& std::abs(to.y() - m_pos.y()) < GCodeFormatter::XYZ_EPSILON)
{
return this->travel_to_z_force(to.z(), comment);
} else if (
std::abs(to.z() - m_pos.z()) < GCodeFormatter::XYZ_EPSILON)
{
return this->travel_to_xy_force(to.head<2>(), comment);
} else {
std::string result{this->get_travel_to_xyz_gcode(to, comment)};
m_pos = to;
return result;
return this->travel_to_xyz_force(to, comment);
}
}
std::string GCodeWriter::get_travel_to_xyz_gcode(const Vec3d &to, const std::string_view comment) const {
std::string GCodeWriter::travel_to_xyz_force(const Vec3d &to, const std::string_view comment) {
GCodeG1Formatter w;
w.emit_xyz(to);
double speed_z = this->config.travel_speed_z.value;
if (speed_z == 0.)
speed_z = this->config.travel_speed.value;
double speed = this->config.travel_speed.value;
const double speed_z = this->config.travel_speed_z.value;
const double distance_xy{(to.head<2>() - m_pos.head<2>()).norm()};
const double distnace_z{std::abs(to.z() - m_pos.z())};
const double time_z = distnace_z / speed_z;
const double time_xy = distance_xy / this->config.travel_speed.value;
const double factor = time_z > 0 ? time_xy / time_z : 1;
if (factor < 1) {
w.emit_f(std::floor((this->config.travel_speed.value * factor + (1 - factor) * speed_z) * 60.0));
} else {
w.emit_f(this->config.travel_speed.value * 60.0);
if (speed_z) {
const Vec3d move{to - m_pos};
const double distance{move.norm()};
const double abs_unit_vector_z{std::abs(move.z())/distance};
// De-compose speed into z vector component according to the movement unit vector.
const double speed_vector_z{abs_unit_vector_z * speed};
if (speed_vector_z > speed_z) {
// Re-compute speed so that the z component is exactly speed_z.
speed = speed_z / abs_unit_vector_z;
}
}
w.emit_f(speed * 60.0);
w.emit_comment(this->config.gcode_comments, comment);
m_pos = to;
return w.string();
}
std::string GCodeWriter::travel_to_z(double z, const std::string_view comment)
{
if (std::abs(m_pos.z() - z) < EPSILON) {
if (std::abs(m_pos.z() - z) < GCodeFormatter::XYZ_EPSILON) {
return "";
} else {
m_pos.z() = z;
return this->get_travel_to_z_gcode(z, comment);
return this->travel_to_z_force(z, comment);
}
}
std::string GCodeWriter::get_travel_to_z_gcode(double z, const std::string_view comment) const
std::string GCodeWriter::travel_to_z_force(double z, const std::string_view comment)
{
double speed = this->config.travel_speed_z.value;
if (speed == 0.)
@ -383,6 +398,7 @@ std::string GCodeWriter::get_travel_to_z_gcode(double z, const std::string_view
w.emit_z(z);
w.emit_f(speed * 60.0);
w.emit_comment(this->config.gcode_comments, comment);
m_pos.z() = z;
return w.string();
}

View File

@ -74,27 +74,46 @@ public:
std::string toolchange(unsigned int extruder_id);
std::string set_speed(double F, const std::string_view comment = {}, const std::string_view cooling_marker = {}) const;
std::string get_travel_to_xy_gcode(const Vec2d &point, const std::string_view comment) const;
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 = {});
/**
* @brief Return gcode with all three axis defined. Optionally adds feedrate.
* @brief Return gcode to travel to the specified point.
* Feed rate is computed based on the vector (to - m_pos).
* Maintains the internal m_pos position.
* Movements less than XYZ_EPSILON generate no output.
*
* Feedrate is added the starting point "from" is specified.
*
* @param from Optional starting point of the travel.
* @param to Where to travel to.
* @param comment Description of the travel purpose.
*/
std::string get_travel_to_xyz_gcode(const Vec3d &to, const std::string_view comment) const;
std::string travel_to_xyz(const Vec3d &to, const std::string_view comment = {});
std::string get_travel_to_z_gcode(double z, const std::string_view comment) const;
std::string travel_to_xy(const Vec2d &point, const std::string_view comment = {});
std::string travel_to_z(double z, 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 = {});
/**
* @brief Generate G-Code to travel to the specified point unconditionally.
* Feed rate is computed based on the vector (to - m_pos).
* Maintains the internal m_pos position.
* The distance test XYZ_EPSILON is not performed.
* @param to The point to travel to.
* @param comment Description of the travel purpose.
*/
std::string travel_to_xyz_force(const Vec3d &to, const std::string_view comment = {});
std::string travel_to_xy_force(const Vec2d &point, const std::string_view comment = {});
std::string travel_to_z_force(double z, const std::string_view comment = {});
/**
* @brief Generate G-Code to move to the specified point while extruding.
* Maintains the internal m_pos position.
* The distance test XYZ_EPSILON is not performed.
* @param point The point to move to.
* @param dE The E-steps to extrude while moving.
* @param comment Description of the movement purpose.
*/
std::string extrude_to_xy(const Vec2d &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 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 retract(bool before_wipe = false);
std::string retract_for_toolchange(bool before_wipe = false);
std::string unretract();
@ -176,6 +195,9 @@ public:
static constexpr const std::array<double, 10> pow_10 { 1., 10., 100., 1000., 10000., 100000., 1000000., 10000000., 100000000., 1000000000.};
static constexpr const std::array<double, 10> pow_10_inv{1./1., 1./10., 1./100., 1./1000., 1./10000., 1./100000., 1./1000000., 1./10000000., 1./100000000., 1./1000000000.};
// Compute XYZ_EPSILON based on XYZF_EXPORT_DIGITS
static constexpr double XYZ_EPSILON = pow_10_inv[XYZF_EXPORT_DIGITS];
// Quantize doubles to a resolution of the G-code.
static double quantize(double v, size_t ndigits) { return std::round(v * pow_10[ndigits]) * pow_10_inv[ndigits]; }
static double quantize_xyzf(double v) { return quantize(v, XYZF_EXPORT_DIGITS); }

View File

@ -78,7 +78,7 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip
);
} else {
gcode += gcodegen.writer().travel_to_xy(gcodegen.point_to_gcode(xy_point), comment);
gcode += gcodegen.writer().get_travel_to_z_gcode(z, comment);
gcode += gcodegen.writer().travel_to_z_force(z, comment);
}
}
gcode += gcodegen.unretract();
@ -100,10 +100,7 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip
gcodegen.m_wipe.reset_path(); // We don't want wiping on the ramming lines.
toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z); // TODO: toolchange_z vs print_z
if (gcodegen.config().wipe_tower) {
deretraction_str += gcodegen.writer().get_travel_to_z_gcode(z, "restore layer Z");
Vec3d position{gcodegen.writer().get_position()};
position.z() = z;
gcodegen.writer().update_position(position);
deretraction_str += gcodegen.writer().travel_to_z_force(z, "restore layer Z");
deretraction_str += gcodegen.unretract();
}
}

View File

@ -5,6 +5,7 @@
#include <memory>
#include "libslic3r/GCode/GCodeWriter.hpp"
#include "libslic3r/GCodeReader.hpp"
using namespace Slic3r;
@ -34,3 +35,503 @@ SCENARIO("set_speed emits values with fixed-point output.", "[GCodeWriter]") {
}
}
}
void check_gcode_feedrate(const std::string& gcode, const GCodeConfig& config, double expected_speed) {
GCodeReader parser;
parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) {
const double travel_speed = config.opt_float("travel_speed");
const double feedrate = line.has_f() ? line.f() : self.f();
CHECK(feedrate == Approx(expected_speed * 60).epsilon(GCodeFormatter::XYZ_EPSILON));
if (line.dist_Z(self) != 0) {
// lift move or lift + change layer
const double travel_speed_z = config.opt_float("travel_speed_z");
if (travel_speed_z) {
Vec3d move{line.dist_X(self), line.dist_Y(self), line.dist_Z(self)};
double move_u_z = move.z() / move.norm();
double travel_speed_ = std::abs(travel_speed_z / move_u_z);
INFO("move Z feedrate Z component is less than or equal to travel_speed_z");
CHECK(feedrate * std::abs(move_u_z) <= Approx(travel_speed_z * 60).epsilon(GCodeFormatter::XYZ_EPSILON));
if (travel_speed_ < travel_speed) {
INFO("move Z at travel speed Z");
CHECK(feedrate == Approx(travel_speed_ * 60).epsilon(GCodeFormatter::XYZ_EPSILON));
INFO("move Z feedrate Z component is equal to travel_speed_z");
CHECK(feedrate * std::abs(move_u_z) == Approx(travel_speed_z * 60).epsilon(GCodeFormatter::XYZ_EPSILON));
} else {
INFO("move Z at travel speed");
CHECK(feedrate == Approx(travel_speed * 60).epsilon(GCodeFormatter::XYZ_EPSILON));
}
} else {
INFO("move Z at travel speed");
CHECK(feedrate == Approx(travel_speed * 60).epsilon(GCodeFormatter::XYZ_EPSILON));
}
} else if (not line.extruding(self)) {
// normal move
INFO("move XY at travel speed");
CHECK(feedrate == Approx(travel_speed * 60));
}
});
}
SCENARIO("travel_speed_z is zero should use travel_speed.", "[GCodeWriter]") {
GIVEN("GCodeWriter instance") {
GCodeWriter writer;
WHEN("travel_speed_z is set to 0") {
writer.config.travel_speed.value = 1000;
writer.config.travel_speed_z.value = 0;
THEN("XYZ move feed rate should be equal to travel_speed") {
const Vec3d move{10, 10, 10};
const double speed = writer.config.travel_speed.value;
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
}
}
}
SCENARIO("travel_speed_z is respected in Z speed component.", "[GCodeWriter]") {
GIVEN("GCodeWriter instance") {
GCodeWriter writer;
WHEN("travel_speed_z is set to 10") {
writer.config.travel_speed.value = 1000;
writer.config.travel_speed_z.value = 10;
THEN("Z move feed rate should be equal to travel_speed_z") {
const Vec3d move{0, 0, 10};
const double speed = writer.config.travel_speed_z.value;
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("-Z move feed rate should be equal to travel_speed_z") {
const Vec3d move{0, 0, -10};
const double speed = writer.config.travel_speed_z.value;
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("XY move feed rate should be equal to travel_speed") {
const Vec3d move{10, 10, 0};
const double speed = writer.config.travel_speed.value;
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("-XY move feed rate should be equal to travel_speed") {
const Vec3d move{-10, 10, 0};
const double speed = writer.config.travel_speed.value;
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("X-Y move feed rate should be equal to travel_speed") {
const Vec3d move{10, -10, 0};
const double speed = writer.config.travel_speed.value;
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("-X-Y move feed rate should be equal to travel_speed") {
const Vec3d move{-10, -10, 0};
const double speed = writer.config.travel_speed.value;
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("XZ move feed rate Z component should be equal to travel_speed_z") {
const Vec3d move{10, 0, 10};
const Vec3d move_u = move / move.norm();
const double speed = std::abs(writer.config.travel_speed_z.value / move_u.z());
Vec3d p1 = writer.get_position();
Vec3d p2 = p1 + move;
std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("-XZ move feed rate Z component should be equal to travel_speed_z") {
const Vec3d move{-10, 0, 10};
const Vec3d move_u = move / move.norm();
const double speed = std::abs(writer.config.travel_speed_z.value / move_u.z());
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("X-Z move feed rate Z component should be equal to travel_speed_z") {
const Vec3d move{10, 0, -10};
const Vec3d move_u = move / move.norm();
const double speed = std::abs(writer.config.travel_speed_z.value / move_u.z());
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("-X-Z move feed rate Z component should be equal to travel_speed_z") {
const Vec3d move{-10, 0, -10};
const Vec3d move_u = move / move.norm();
const double speed = std::abs(writer.config.travel_speed_z.value / move_u.z());
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("YZ move feed rate Z component should be equal to travel_speed_z") {
const Vec3d move{0, 10, 10};
const Vec3d move_u = move / move.norm();
const double speed = std::abs(writer.config.travel_speed_z.value / move_u.z());
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("-YZ move feed rate Z component should be equal to travel_speed_z") {
const Vec3d move{0, -10, 10};
const Vec3d move_u = move / move.norm();
const double speed = std::abs(writer.config.travel_speed_z.value / move_u.z());
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("Y-Z move feed rate Z component should be equal to travel_speed_z") {
const Vec3d move{0, 10, -10};
const Vec3d move_u = move / move.norm();
const double speed = std::abs(writer.config.travel_speed_z.value / move_u.z());
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("-Y-Z move feed rate Z component should be equal to travel_speed_z") {
const Vec3d move{0, -10, -10};
const Vec3d move_u = move / move.norm();
const double speed = std::abs(writer.config.travel_speed_z.value / move_u.z());
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("XYZ move feed rate Z component should be equal to travel_speed_z") {
const Vec3d move{10, 10, 10};
const Vec3d move_u = move / move.norm();
const double speed = std::abs(writer.config.travel_speed_z.value / move_u.z());
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("-XYZ move feed rate Z component should be equal to travel_speed_z") {
const Vec3d move{-10, 10, 10};
const Vec3d move_u = move / move.norm();
const double speed = std::abs(writer.config.travel_speed_z.value / move_u.z());
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("X-YZ move feed rate Z component should be equal to travel_speed_z") {
const Vec3d move{10, -10, 10};
const Vec3d move_u = move / move.norm();
const double speed = std::abs(writer.config.travel_speed_z.value / move_u.z());
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("-X-YZ move feed rate Z component should be equal to travel_speed_z") {
const Vec3d move{-10, -10, 10};
const Vec3d move_u = move / move.norm();
const double speed = std::abs(writer.config.travel_speed_z.value / move_u.z());
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("XY-Z move feed rate Z component should be equal to travel_speed_z") {
const Vec3d move{10, 10, -10};
const Vec3d move_u = move / move.norm();
const double speed = std::abs(writer.config.travel_speed_z.value / move_u.z());
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("-XY-Z move feed rate Z component should be equal to travel_speed_z") {
const Vec3d move{-10, 10, -10};
const Vec3d move_u = move / move.norm();
const double speed = std::abs(writer.config.travel_speed_z.value / move_u.z());
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("X-Y-Z move feed rate Z component should be equal to travel_speed_z") {
const Vec3d move{10, -10, -10};
const Vec3d move_u = move / move.norm();
const double speed = std::abs(writer.config.travel_speed_z.value / move_u.z());
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("-X-Y-Z move feed rate Z component should be equal to travel_speed_z") {
const Vec3d move{-10, -10, -10};
const Vec3d move_u = move / move.norm();
const double speed = std::abs(writer.config.travel_speed_z.value / move_u.z());
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
}
}
}
TEST_CASE("GCodeWriter emits G1 code correctly according to XYZF_EXPORT_DIGITS", "[GCodeWriter]") {
GCodeWriter writer;
SECTION("Check quantize") {
CHECK(GCodeFormatter::quantize(1.0,0) == 1.);
CHECK(GCodeFormatter::quantize(0.0,0) == 0.);
CHECK(GCodeFormatter::quantize(0.1,0) == 0);
CHECK(GCodeFormatter::quantize(1.0,1) == 1.);
CHECK(GCodeFormatter::quantize(0.0,1) == 0.);
CHECK(GCodeFormatter::quantize(0.1,1) == Approx(0.1));
CHECK(GCodeFormatter::quantize(0.01,1) == 0.);
CHECK(GCodeFormatter::quantize(1.0,2) == 1.);
CHECK(GCodeFormatter::quantize(0.0,2) == 0.);
CHECK(GCodeFormatter::quantize(0.1,2) == Approx(0.1));
CHECK(GCodeFormatter::quantize(0.01,2) == Approx(0.01));
CHECK(GCodeFormatter::quantize(0.001,2) == 0.);
CHECK(GCodeFormatter::quantize(1.0,3) == 1.);
CHECK(GCodeFormatter::quantize(0.0,3) == 0.);
CHECK(GCodeFormatter::quantize(0.1,3) == Approx(0.1));
CHECK(GCodeFormatter::quantize(0.01,3) == Approx(0.01));
CHECK(GCodeFormatter::quantize(0.001,3) == Approx(0.001));
CHECK(GCodeFormatter::quantize(0.0001,3) == 0.);
CHECK(GCodeFormatter::quantize(1.0,4) == 1.);
CHECK(GCodeFormatter::quantize(0.0,4) == 0.);
CHECK(GCodeFormatter::quantize(0.1,4) == Approx(0.1));
CHECK(GCodeFormatter::quantize(0.01,4) == Approx(0.01));
CHECK(GCodeFormatter::quantize(0.001,4) == Approx(0.001));
CHECK(GCodeFormatter::quantize(0.0001,4) == Approx(0.0001));
CHECK(GCodeFormatter::quantize(0.00001,4) == 0.);
CHECK(GCodeFormatter::quantize(1.0,5) == 1.);
CHECK(GCodeFormatter::quantize(0.0,5) == 0.);
CHECK(GCodeFormatter::quantize(0.1,5) == Approx(0.1));
CHECK(GCodeFormatter::quantize(0.01,5) == Approx(0.01));
CHECK(GCodeFormatter::quantize(0.001,5) == Approx(0.001));
CHECK(GCodeFormatter::quantize(0.0001,5) == Approx(0.0001));
CHECK(GCodeFormatter::quantize(0.00001,5) == Approx(0.00001));
CHECK(GCodeFormatter::quantize(0.000001,5) == 0.);
CHECK(GCodeFormatter::quantize(1.0,6) == 1.);
CHECK(GCodeFormatter::quantize(0.0,6) == 0.);
CHECK(GCodeFormatter::quantize(0.1,6) == Approx(0.1));
CHECK(GCodeFormatter::quantize(0.01,6) == Approx(0.01));
CHECK(GCodeFormatter::quantize(0.001,6) == Approx(0.001));
CHECK(GCodeFormatter::quantize(0.0001,6) == Approx(0.0001));
CHECK(GCodeFormatter::quantize(0.00001,6) == Approx(0.00001));
CHECK(GCodeFormatter::quantize(0.000001,6) == Approx(0.000001));
CHECK(GCodeFormatter::quantize(0.0000001,6) == 0.);
}
SECTION("Check pow_10") {
// IEEE 754 floating point numbers can represent these numbers EXACTLY.
CHECK(GCodeFormatter::pow_10[0] == 1.);
CHECK(GCodeFormatter::pow_10[1] == 10.);
CHECK(GCodeFormatter::pow_10[2] == 100.);
CHECK(GCodeFormatter::pow_10[3] == 1000.);
CHECK(GCodeFormatter::pow_10[4] == 10000.);
CHECK(GCodeFormatter::pow_10[5] == 100000.);
CHECK(GCodeFormatter::pow_10[6] == 1000000.);
CHECK(GCodeFormatter::pow_10[7] == 10000000.);
CHECK(GCodeFormatter::pow_10[8] == 100000000.);
CHECK(GCodeFormatter::pow_10[9] == 1000000000.);
}
SECTION("Check pow_10_inv") {
// IEEE 754 floating point numbers can NOT represent these numbers exactly.
CHECK(GCodeFormatter::pow_10_inv[0] == 1.);
CHECK(GCodeFormatter::pow_10_inv[1] == 0.1);
CHECK(GCodeFormatter::pow_10_inv[2] == 0.01);
CHECK(GCodeFormatter::pow_10_inv[3] == 0.001);
CHECK(GCodeFormatter::pow_10_inv[4] == 0.0001);
CHECK(GCodeFormatter::pow_10_inv[5] == 0.00001);
CHECK(GCodeFormatter::pow_10_inv[6] == 0.000001);
CHECK(GCodeFormatter::pow_10_inv[7] == 0.0000001);
CHECK(GCodeFormatter::pow_10_inv[8] == 0.00000001);
CHECK(GCodeFormatter::pow_10_inv[9] == 0.000000001);
}
SECTION("travel_to_z Emit G1 code for very significant movement") {
double z1 = 10.0;
std::string result1{ writer.travel_to_z(z1) };
CHECK(result1 == "G1 Z10 F7800\n");
double z2 = z1 * 2;
std::string result2{ writer.travel_to_z(z2) };
CHECK(result2 == "G1 Z20 F7800\n");
}
SECTION("travel_to_z Emit G1 code for significant movement") {
double z1 = 10.0;
std::string result1{ writer.travel_to_z(z1) };
CHECK(result1 == "G1 Z10 F7800\n");
// This should test with XYZ_EPSILON exactly,
// but IEEE 754 floating point numbers cannot pass the test.
double z2 = z1 + GCodeFormatter::XYZ_EPSILON * 1.001;
std::string result2{ writer.travel_to_z(z2) };
std::ostringstream oss;
oss << "G1 Z"
<< GCodeFormatter::quantize_xyzf(z2)
<< " F7800\n";
CHECK(result2 == oss.str());
}
SECTION("travel_to_z Do not emit G1 code for insignificant movement") {
double z1 = 10.0;
std::string result1{ writer.travel_to_z(z1) };
CHECK(result1 == "G1 Z10 F7800\n");
// Movement smaller than XYZ_EPSILON
double z2 = z1 + (GCodeFormatter::XYZ_EPSILON * 0.999);
std::string result2{ writer.travel_to_z(z2) };
CHECK(result2 == "");
double z3 = z1 + (GCodeFormatter::XYZ_EPSILON * 0.1);
std::string result3{ writer.travel_to_z(z3) };
CHECK(result3 == "");
}
SECTION("travel_to_xyz Emit G1 code for very significant movement") {
Vec3d v1{10.0, 10.0, 10.0};
std::string result1{ writer.travel_to_xyz(v1) };
CHECK(result1 == "G1 X10 Y10 Z10 F7800\n");
Vec3d v2 = v1 * 2;
std::string result2{ writer.travel_to_xyz(v2) };
CHECK(result2 == "G1 X20 Y20 Z20 F7800\n");
}
SECTION("travel_to_xyz Emit G1 code for significant XYZ movement") {
Vec3d v1{10.0, 10.0, 10.0};
std::string result1{ writer.travel_to_xyz(v1) };
CHECK(result1 == "G1 X10 Y10 Z10 F7800\n");
Vec3d v2 = v1;
// This should test with XYZ_EPSILON exactly,
// but IEEE 754 floating point numbers cannot pass the test.
v2.array() += GCodeFormatter::XYZ_EPSILON * 1.001;
std::string result2{ writer.travel_to_xyz(v2) };
std::ostringstream oss;
oss << "G1 X"
<< GCodeFormatter::quantize_xyzf(v2.x())
<< " Y"
<< GCodeFormatter::quantize_xyzf(v2.y())
<< " Z"
<< GCodeFormatter::quantize_xyzf(v2.z())
<< " F7800\n";
CHECK(result2 == oss.str());
}
SECTION("travel_to_xyz Emit G1 code for significant X movement") {
Vec3d v1{10.0, 10.0, 10.0};
std::string result1{ writer.travel_to_xyz(v1) };
CHECK(result1 == "G1 X10 Y10 Z10 F7800\n");
Vec3d v2 = v1;
// This should test with XYZ_EPSILON exactly,
// but IEEE 754 floating point numbers cannot pass the test.
v2.x() += GCodeFormatter::XYZ_EPSILON * 1.001;
std::string result2{ writer.travel_to_xyz(v2) };
std::ostringstream oss;
// Only X needs to be emitted in this case,
// but this is how the code currently works.
oss << "G1 X"
<< GCodeFormatter::quantize_xyzf(v2.x())
<< " Y"
<< GCodeFormatter::quantize_xyzf(v2.y())
<< " F7800\n";
CHECK(result2 == oss.str());
}
SECTION("travel_to_xyz Emit G1 code for significant Y movement") {
Vec3d v1{10.0, 10.0, 10.0};
std::string result1{ writer.travel_to_xyz(v1) };
CHECK(result1 == "G1 X10 Y10 Z10 F7800\n");
Vec3d v2 = v1;
// This should test with XYZ_EPSILON exactly,
// but IEEE 754 floating point numbers cannot pass the test.
v2.y() += GCodeFormatter::XYZ_EPSILON * 1.001;
std::string result2{ writer.travel_to_xyz(v2) };
std::ostringstream oss;
// Only Y needs to be emitted in this case,
// but this is how the code currently works.
oss << "G1 X"
<< GCodeFormatter::quantize_xyzf(v2.x())
<< " Y"
<< GCodeFormatter::quantize_xyzf(v2.y())
<< " F7800\n";
CHECK(result2 == oss.str());
}
SECTION("travel_to_xyz Emit G1 code for significant Z movement") {
Vec3d v1{10.0, 10.0, 10.0};
std::string result1{ writer.travel_to_xyz(v1) };
CHECK(result1 == "G1 X10 Y10 Z10 F7800\n");
Vec3d v2 = v1;
// This should test with XYZ_EPSILON exactly,
// but IEEE 754 floating point numbers cannot pass the test.
v2.z() += GCodeFormatter::XYZ_EPSILON * 1.001;
std::string result2{ writer.travel_to_xyz(v2) };
std::ostringstream oss;
oss << "G1 Z"
<< GCodeFormatter::quantize_xyzf(v2.z())
<< " F7800\n";
CHECK(result2 == oss.str());
}
SECTION("travel_to_xyz Do not emit G1 code for insignificant movement") {
Vec3d v1{10.0, 10.0, 10.0};
std::string result1{ writer.travel_to_xyz(v1) };
CHECK(result1 == "G1 X10 Y10 Z10 F7800\n");
// Movement smaller than XYZ_EPSILON
Vec3d v2 = v1;
v2.array() += GCodeFormatter::XYZ_EPSILON * 0.999;
std::string result2{ writer.travel_to_xyz(v2) };
CHECK(result2 == "");
Vec3d v3 = v1;
v3.array() += GCodeFormatter::XYZ_EPSILON * 0.1;
std::string result3{ writer.travel_to_xyz(v3) };
CHECK(result3 == "");
}
}

View File

@ -5,8 +5,9 @@
#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include <libslic3r/GCodeReader.hpp>
#include <libslic3r/Config.hpp>
#include "libslic3r/GCodeReader.hpp"
#include "libslic3r/GCode/GCodeWriter.hpp"
#include "libslic3r/Config.hpp"
#include "test_data.hpp"
#include <regex>
@ -61,6 +62,10 @@ void check_gcode(std::initializer_list<TestMesh> meshes, const DynamicPrintConfi
const double retract_restart_extra = config.option<ConfigOptionFloats>("retract_restart_extra")->get_at(tool);
const double retract_restart_extra_toolchange = config.option<ConfigOptionFloats>("retract_restart_extra_toolchange")->get_at(tool);
const double travel_speed = config.opt_float("travel_speed");
const double feedrate = line.has_f() ? line.f() : self.f();
if (line.dist_Z(self) != 0) {
// lift move or lift + change layer
const double retract_lift = config.option<ConfigOptionFloats>("retract_lift")->get_at(tool);
@ -89,9 +94,26 @@ void check_gcode(std::initializer_list<TestMesh> meshes, const DynamicPrintConfi
lift_dist = 0;
lifted = false;
}
const double feedrate = line.has_f() ? line.f() : self.f();
const double travel_speed_z = config.opt_float("travel_speed_z");
if (travel_speed_z) {
Vec3d move{line.dist_X(self), line.dist_Y(self), line.dist_Z(self)};
const double move_u_z = move.z() / move.norm();
const double travel_speed_ = std::abs(travel_speed_z / move_u_z);
INFO("move Z feedrate Z component is less than or equal to travel_speed_z");
CHECK(feedrate * std::abs(move_u_z) <= Approx(travel_speed_z * 60).epsilon(GCodeFormatter::XYZ_EPSILON));
if (travel_speed_ < travel_speed) {
INFO("move Z at travel speed Z");
CHECK(feedrate == Approx(travel_speed_ * 60).epsilon(GCodeFormatter::XYZ_EPSILON));
INFO("move Z feedrate Z component is equal to travel_speed_z");
CHECK(feedrate * std::abs(move_u_z) == Approx(travel_speed_z * 60).epsilon(GCodeFormatter::XYZ_EPSILON));
} else {
INFO("move Z at travel speed");
CHECK(feedrate == Approx(config.opt_float("travel_speed") * 60));
CHECK(feedrate == Approx(travel_speed * 60).epsilon(GCodeFormatter::XYZ_EPSILON));
}
} else {
INFO("move Z at travel speed");
CHECK(feedrate == Approx(travel_speed * 60).epsilon(GCodeFormatter::XYZ_EPSILON));
}
}
if (line.retracting(self)) {
retracted[tool] = true;
@ -144,7 +166,7 @@ void test_slicing(std::initializer_list<TestMesh> meshes, DynamicPrintConfig& co
}
TEST_CASE("Slicing with retraction and lifing", "[retraction]") {
TEST_CASE("Slicing with retraction and lifting", "[retraction]") {
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
config.set_deserialize_strict({
{ "nozzle_diameter", "0.6,0.6,0.6,0.6" },
@ -173,6 +195,37 @@ TEST_CASE("Slicing with retraction and lifing", "[retraction]") {
}
}
TEST_CASE("Slicing with retraction and lifting with travel_speed_z=10", "[retraction]") {
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
config.set_deserialize_strict({
{ "nozzle_diameter", "0.6,0.6,0.6,0.6" },
{ "first_layer_height", config.opt_float("layer_height") },
{ "first_layer_speed", "100%" },
{ "start_gcode", "" }, // To avoid dealing with the nozzle lift in start G-code
{ "retract_length", "1.5" },
{ "retract_before_travel", "3" },
{ "retract_layer_change", "1" },
{ "only_retract_when_crossing_perimeters", 0 },
{ "travel_speed", "600" },
{ "travel_speed_z", "10" },
});
SECTION("Standard run") {
test_slicing({TestMesh::cube_20x20x20}, config);
}
SECTION("With duplicate cube") {
test_slicing({TestMesh::cube_20x20x20}, config, 2);
}
SECTION("Dual extruder with multiple skirt layers") {
config.set_deserialize_strict({
{"infill_extruder", 2},
{"skirts", 4},
{"skirt_height", 3},
});
test_slicing({TestMesh::cube_20x20x20}, config);
}
}
TEST_CASE("Z moves", "[retraction]") {
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();