Implement ramping layer change using a tag in gcode

During layer change, instead of generating the gcode, generate a placeholder tag. Then at the end of layer processing replace this tag with a ramping travel move.
This solves the issue, that one does not know the starting point of the current layer where the layer change gcode would be originally generate.
The ramping layer changes uses smoothing of the ramping travel. Also it is adjusted in such a way that it increases the ramp angle when the travel is too short, to always reach the next layer.
This commit is contained in:
SachCZ 2024-01-05 12:17:03 +01:00 committed by Martin Šach
parent 24e3254697
commit 7f397cd7b3
14 changed files with 241 additions and 320 deletions

View File

@ -193,8 +193,6 @@ set(SLIC3R_SOURCES
GCode/AvoidCrossingPerimeters.hpp GCode/AvoidCrossingPerimeters.hpp
GCode/Travels.cpp GCode/Travels.cpp
GCode/Travels.hpp GCode/Travels.hpp
GCode/LayerChanges.cpp
GCode/LayerChanges.hpp
GCode.cpp GCode.cpp
GCode.hpp GCode.hpp
GCodeReader.cpp GCodeReader.cpp

View File

@ -35,7 +35,6 @@
#include "GCode/WipeTower.hpp" #include "GCode/WipeTower.hpp"
#include "GCode/WipeTowerIntegration.hpp" #include "GCode/WipeTowerIntegration.hpp"
#include "GCode/Travels.hpp" #include "GCode/Travels.hpp"
#include "GCode/LayerChanges.hpp"
#include "Point.hpp" #include "Point.hpp"
#include "Polygon.hpp" #include "Polygon.hpp"
#include "PrintConfig.hpp" #include "PrintConfig.hpp"
@ -2081,6 +2080,50 @@ AABBTreeLines::LinesDistancer<Linef> get_previous_layer_distancer(
} }
} }
std::string GCodeGenerator::get_layer_change_gcode(const Vec3d& from, const Vec3d& to, const unsigned extruder_id) {
const Polyline xy_path{
this->gcode_to_point(from.head<2>()),
this->gcode_to_point(to.head<2>())
};
using namespace GCode::Impl::Travels;
ElevatedTravelParams elevation_params{
get_elevated_traval_params(xy_path, this->m_config, extruder_id)};
const double initial_elevation = from.z();
const double z_change = to.z() - from.z();
elevation_params.lift_height = std::max(z_change, elevation_params.lift_height);
const double path_length = unscaled(xy_path.length());
const double lift_at_travel_end =
(elevation_params.lift_height / elevation_params.slope_end * path_length);
if (lift_at_travel_end < z_change) {
elevation_params.lift_height = z_change;
elevation_params.slope_end = path_length;
}
const std::vector<double> ensure_points_at_distances = linspace(
elevation_params.slope_end - elevation_params.blend_width / 2.0,
elevation_params.slope_end + elevation_params.blend_width / 2.0,
elevation_params.parabola_points_count
);
Points3 travel{generate_elevated_travel(
xy_path.points, ensure_points_at_distances, initial_elevation,
ElevatedTravelFormula{elevation_params}
)};
std::string travel_gcode;
Vec3d previous_point{this->point_to_gcode(travel.front())};
for (const Vec3crd& point : tcb::span{travel}.subspan(1)) {
const Vec3d gcode_point{this->point_to_gcode(point)};
travel_gcode += this->m_writer.get_travel_to_xyz_gcode(previous_point, gcode_point, "layer change");
previous_point = gcode_point;
}
return travel_gcode;
}
// In sequential mode, process_layer is called once per each object and its copy, // In sequential mode, process_layer is called once per each object and its copy,
// therefore layers will contain a single entry and single_object_instance_idx will point to the copy of the object. // therefore layers will contain a single entry and single_object_instance_idx will point to the copy of the object.
// In non-sequential mode, process_layer is called per each print_z height with all object and support layers accumulated. // In non-sequential mode, process_layer is called per each print_z height with all object and support layers accumulated.
@ -2150,6 +2193,7 @@ LayerResult GCodeGenerator::process_layer(
m_enable_loop_clipping = !enable; m_enable_loop_clipping = !enable;
} }
std::string gcode; std::string gcode;
assert(is_decimal_separator_point()); // for the sprintfs assert(is_decimal_separator_point()); // for the sprintfs
@ -2168,6 +2212,7 @@ LayerResult GCodeGenerator::process_layer(
m_last_layer_z = static_cast<float>(print_z); m_last_layer_z = static_cast<float>(print_z);
m_max_layer_z = std::max(m_max_layer_z, m_last_layer_z); m_max_layer_z = std::max(m_max_layer_z, m_last_layer_z);
m_last_height = height; m_last_height = height;
m_current_layer_first_position = std::nullopt;
// Set new layer - this will change Z and force a retraction if retract_layer_change is enabled. // Set new layer - this will change Z and force a retraction if retract_layer_change is enabled.
if (! print.config().before_layer_gcode.value.empty()) { if (! print.config().before_layer_gcode.value.empty()) {
@ -2179,7 +2224,7 @@ LayerResult GCodeGenerator::process_layer(
print.config().before_layer_gcode.value, m_writer.extruder()->id(), &config) print.config().before_layer_gcode.value, m_writer.extruder()->id(), &config)
+ "\n"; + "\n";
} }
gcode += this->change_layer(previous_layer_z, print_z, result.spiral_vase_enable); // this will increase m_layer_index gcode += this->change_layer(previous_layer_z, print_z); // this will increase m_layer_index
m_layer = &layer; m_layer = &layer;
if (this->line_distancer_is_required(layer_tools.extruders) && this->m_layer != nullptr && this->m_layer->lower_layer != nullptr) { if (this->line_distancer_is_required(layer_tools.extruders) && this->m_layer != nullptr && this->m_layer->lower_layer != nullptr) {
this->m_previous_layer_distancer = GCode::Impl::get_previous_layer_distancer(layers, layer.lower_layer->lslices); this->m_previous_layer_distancer = GCode::Impl::get_previous_layer_distancer(layers, layer.lower_layer->lslices);
@ -2305,6 +2350,33 @@ LayerResult GCodeGenerator::process_layer(
is_anything_overridden, false /* print_wipe_extrusions */); is_anything_overridden, false /* print_wipe_extrusions */);
} }
// During layer change the starting position of next layer is now known.
// The solution is thus to emplace a temporary tag to the gcode, cache the postion and
// replace the tag later. The tag is Layer_Change_Travel, the cached position is
// m_current_layer_first_position and it is replaced here.
const std::string tag = GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change_Travel);
std::string layer_change_gcode;
const bool do_ramping_layer_change = (
m_previous_layer_last_position
&& m_current_layer_first_position
&& m_layer_change_extruder_id
&& !result.spiral_vase_enable
&& print_z > previous_layer_z
&& EXTRUDER_CONFIG(travel_ramping_lift)
&& EXTRUDER_CONFIG(travel_slope) > 0 && EXTRUDER_CONFIG(travel_slope) < 90
);
if (do_ramping_layer_change) {
layer_change_gcode = this->get_layer_change_gcode(*m_previous_layer_last_position, *m_current_layer_first_position, *m_layer_change_extruder_id);
} else {
if (!m_current_layer_first_position) {
throw std::runtime_error("Destination is required for layer change!");
}
layer_change_gcode = this->writer().get_travel_to_z_gcode(m_current_layer_first_position->z(), "simple layer change");
}
boost::algorithm::replace_first(gcode, tag, layer_change_gcode);
BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z << BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z <<
log_memory_info(); log_memory_info();
@ -2604,63 +2676,10 @@ std::string GCodeGenerator::preamble()
return gcode; return gcode;
} }
std::optional<std::string> GCodeGenerator::get_helical_layer_change_gcode(
const coordf_t previous_layer_z,
const coordf_t print_z,
const std::string& comment
) {
if (!this->last_pos_defined()) {
return std::nullopt;
}
const double circle_radius{2};
const unsigned n_gon_points_count{16};
const Point n_gon_start_point{this->last_pos()};
GCode::Impl::LayerChanges::Bed bed{
this->m_config.bed_shape.values,
circle_radius * 2
};
if (!bed.contains_within_padding(this->point_to_gcode(n_gon_start_point))) {
return std::nullopt;
}
const Vec2crd n_gon_vector{scaled(Vec2d{
(bed.centroid - this->point_to_gcode(n_gon_start_point)).normalized() * circle_radius
})};
const Point n_gon_centeroid{n_gon_start_point + n_gon_vector};
const Polygon n_gon{GCode::Impl::LayerChanges::generate_regular_polygon(
n_gon_centeroid,
n_gon_start_point,
n_gon_points_count
)};
const double n_gon_circumference = unscaled(n_gon.length());
const double z_change{print_z - previous_layer_z};
Points3 helix{GCode::Impl::Travels::generate_elevated_travel(
n_gon.points,
{},
previous_layer_z,
[&](const double distance){
return distance / n_gon_circumference * z_change;
}
)};
helix.emplace_back(to_3d(this->last_pos(), scaled(print_z)));
return this->generate_travel_gcode(helix, comment);
}
// called by GCodeGenerator::process_layer() // called by GCodeGenerator::process_layer()
std::string GCodeGenerator::change_layer( std::string GCodeGenerator::change_layer(
coordf_t previous_layer_z, coordf_t previous_layer_z,
coordf_t print_z, coordf_t print_z
const bool spiral_vase_enabled
) { ) {
std::string gcode; std::string gcode;
if (m_layer_count > 0) if (m_layer_count > 0)
@ -2670,31 +2689,16 @@ std::string GCodeGenerator::change_layer(
if (EXTRUDER_CONFIG(retract_layer_change)) if (EXTRUDER_CONFIG(retract_layer_change))
gcode += this->retract_and_wipe(); gcode += this->retract_and_wipe();
const std::string comment{"move to next layer (" + std::to_string(m_layer_index) + ")"}; Vec3d new_position = this->writer().get_position();
new_position.z() = print_z;
this->writer().update_position(new_position);
bool do_helical_layer_change{ m_previous_layer_last_position = this->m_last_pos_defined ?
!spiral_vase_enabled std::optional{to_3d(this->point_to_gcode(this->last_pos()), previous_layer_z)} :
&& print_z > previous_layer_z std::nullopt;
&& EXTRUDER_CONFIG(retract_layer_change)
&& EXTRUDER_CONFIG(retract_length) > 0
&& EXTRUDER_CONFIG(travel_ramping_lift)
&& EXTRUDER_CONFIG(travel_slope) > 0 && EXTRUDER_CONFIG(travel_slope) < 90
};
const std::optional<std::string> helix_gcode{ gcode += GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change_Travel);
do_helical_layer_change ? this->m_layer_change_extruder_id = m_writer.extruder()->id();
this->get_helical_layer_change_gcode(
m_config.z_offset.value + previous_layer_z,
m_config.z_offset.value + print_z,
comment
) :
std::nullopt
};
gcode += (
helix_gcode ?
*helix_gcode :
m_writer.travel_to_z(m_config.z_offset.value + print_z, comment)
);
// forget last wiping path as wiping after raising Z is pointless // forget last wiping path as wiping after raising Z is pointless
m_wipe.reset_path(); m_wipe.reset_path();
@ -2963,6 +2967,16 @@ std::string GCodeGenerator::_extrude(
std::string gcode; std::string gcode;
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;
if (!m_current_layer_first_position) {
// Make the first travel just one G1.
const Vec3crd point = to_3d(path.front().point, scaled(this->m_last_layer_z + this->m_config.z_offset.value));
const Vec3d gcode_point = to_3d(this->point_to_gcode(point.head<2>()), unscaled(point.z()));
this->set_last_pos(path.front().point);
this->writer().update_position(gcode_point);
gcode += this->writer().get_travel_to_xy_gcode(gcode_point.head<2>(), "move to first layer point");
gcode += this->writer().get_travel_to_z_gcode(gcode_point.z(), "move to first layer point");
m_current_layer_first_position = gcode_point;
} else {
// go to first point of extrusion path // go to first point of extrusion path
if (!m_last_pos_defined) { if (!m_last_pos_defined) {
const double z = this->m_last_layer_z + this->m_config.z_offset.value; const double z = this->m_last_layer_z + this->m_config.z_offset.value;
@ -2975,7 +2989,9 @@ std::string GCodeGenerator::_extrude(
comment += description; comment += description;
comment += description_bridge; comment += description_bridge;
comment += " point"; comment += " point";
gcode += this->travel_to(path.front().point, path_attr.role, comment); const std::string travel_gcode{this->travel_to(path.front().point, path_attr.role, comment)};
gcode += travel_gcode;
}
} }
// compensate retraction // compensate retraction
@ -3217,9 +3233,13 @@ std::string GCodeGenerator::generate_travel_gcode(
// 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)
gcode += this->m_writer.set_travel_acceleration(acceleration); gcode += this->m_writer.set_travel_acceleration(acceleration);
for (const Vec3crd& point : travel) { Vec3d previous_point{this->point_to_gcode(travel.front())};
gcode += this->m_writer.travel_to_xyz(to_3d(this->point_to_gcode(point.head<2>()), unscaled(point.z())), comment); for (const Vec3crd& point : tcb::span{travel}.subspan(1)) {
const Vec3d gcode_point{this->point_to_gcode(point)};
gcode += this->m_writer.travel_to_xyz(previous_point, gcode_point, comment);
this->set_last_pos(point.head<2>()); this->set_last_pos(point.head<2>());
previous_point = gcode_point;
} }
if (! GCodeWriter::supports_separate_travel_acceleration(config().gcode_flavor)) { if (! GCodeWriter::supports_separate_travel_acceleration(config().gcode_flavor)) {
@ -3351,6 +3371,14 @@ std::string GCodeGenerator::travel_to(const Point &point, ExtrusionRole role, st
const double retract_length = this->m_config.retract_length.get_at(extruder_id); const double retract_length = this->m_config.retract_length.get_at(extruder_id);
bool can_be_flat{!needs_retraction || retract_length == 0}; 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 double initial_elevation = this->m_last_layer_z + this->m_config.z_offset.value;
const double upper_limit = this->m_config.retract_lift_below.get_at(extruder_id);
const double lower_limit = this->m_config.retract_lift_above.get_at(extruder_id);
if ((lower_limit > 0 && initial_elevation < lower_limit) ||
(upper_limit > 0 && initial_elevation > upper_limit)) {
can_be_flat = true;
}
const Points3 travel = ( const Points3 travel = (
can_be_flat ? can_be_flat ?
GCode::Impl::Travels::generate_flat_travel(xy_path.points, initial_elevation) : GCode::Impl::Travels::generate_flat_travel(xy_path.points, initial_elevation) :

View File

@ -127,11 +127,25 @@ public:
const Point& last_pos() const { return m_last_pos; } const Point& last_pos() const { return m_last_pos; }
// Convert coordinates of the active object to G-code coordinates, possibly adjusted for extruder offset. // Convert coordinates of the active object to G-code coordinates, possibly adjusted for extruder offset.
template<typename Derived> template<typename Derived>
Vec2d point_to_gcode(const Eigen::MatrixBase<Derived> &point) const { Eigen::Matrix<double, Derived::SizeAtCompileTime, 1, Eigen::DontAlign> point_to_gcode(const Eigen::MatrixBase<Derived> &point) const {
static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "GCodeGenerator::point_to_gcode(): first parameter is not a 2D vector"); static_assert(
Derived::IsVectorAtCompileTime,
"GCodeGenerator::point_to_gcode(): first parameter is not a vector"
);
static_assert(
int(Derived::SizeAtCompileTime) == 2 || int(Derived::SizeAtCompileTime) == 3,
"GCodeGenerator::point_to_gcode(): first parameter is not a 2D or 3D vector"
);
if constexpr (Derived::SizeAtCompileTime == 2) {
return Vec2d(unscaled<double>(point.x()), unscaled<double>(point.y())) + m_origin return Vec2d(unscaled<double>(point.x()), unscaled<double>(point.y())) + m_origin
- m_config.extruder_offset.get_at(m_writer.extruder()->id()); - m_config.extruder_offset.get_at(m_writer.extruder()->id());
} else {
const Vec2d gcode_point_xy{this->point_to_gcode(point.template head<2>())};
return to_3d(gcode_point_xy, unscaled(point.z()));
} }
}
// Convert coordinates of the active object to G-code coordinates, possibly adjusted for extruder offset and quantized to G-code resolution. // Convert coordinates of the active object to G-code coordinates, possibly adjusted for extruder offset and quantized to G-code resolution.
template<typename Derived> template<typename Derived>
Vec2d point_to_gcode_quantized(const Eigen::MatrixBase<Derived> &point) const { Vec2d point_to_gcode_quantized(const Eigen::MatrixBase<Derived> &point) const {
@ -216,6 +230,9 @@ private:
static ObjectsLayerToPrint collect_layers_to_print(const PrintObject &object); static ObjectsLayerToPrint collect_layers_to_print(const PrintObject &object);
static std::vector<std::pair<coordf_t, ObjectsLayerToPrint>> collect_layers_to_print(const Print &print); static std::vector<std::pair<coordf_t, ObjectsLayerToPrint>> collect_layers_to_print(const Print &print);
/** @brief Generates ramping travel gcode for layer change. */
std::string get_layer_change_gcode(const Vec3d& from, const Vec3d& to, const unsigned extruder_id);
LayerResult process_layer( LayerResult process_layer(
const Print &print, const Print &print,
// Set of object & print layers of the same PrintObject and with the same print_z. // Set of object & print layers of the same PrintObject and with the same print_z.
@ -253,15 +270,9 @@ private:
bool last_pos_defined() const { return m_last_pos_defined; } bool last_pos_defined() const { return m_last_pos_defined; }
void set_extruders(const std::vector<unsigned int> &extruder_ids); void set_extruders(const std::vector<unsigned int> &extruder_ids);
std::string preamble(); std::string preamble();
std::optional<std::string> get_helical_layer_change_gcode(
const coordf_t previous_layer_z,
const coordf_t print_z,
const std::string& comment
);
std::string change_layer( std::string change_layer(
coordf_t previous_layer_z, coordf_t previous_layer_z,
coordf_t print_z, coordf_t print_z
const bool spiral_vase_enabled
); );
std::string extrude_entity(const ExtrusionEntityReference &entity, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed = -1.); std::string extrude_entity(const ExtrusionEntityReference &entity, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed = -1.);
std::string extrude_loop(const ExtrusionLoop &loop, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed = -1.); std::string extrude_loop(const ExtrusionLoop &loop, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed = -1.);
@ -412,7 +423,10 @@ private:
Point m_last_pos; Point m_last_pos;
bool m_last_pos_defined; bool m_last_pos_defined;
std::optional<Vec3d> m_previous_layer_last_position;
// This needs to be populated during the layer processing!
std::optional<Vec3d> m_current_layer_first_position;
std::optional<unsigned> m_layer_change_extruder_id;
std::unique_ptr<CoolingBuffer> m_cooling_buffer; std::unique_ptr<CoolingBuffer> m_cooling_buffer;
std::unique_ptr<SpiralVase> m_spiral_vase; std::unique_ptr<SpiralVase> m_spiral_vase;
std::unique_ptr<GCodeFindReplace> m_find_replace; std::unique_ptr<GCodeFindReplace> m_find_replace;

View File

@ -57,6 +57,7 @@ const std::vector<std::string> GCodeProcessor::Reserved_Tags = {
"HEIGHT:", "HEIGHT:",
"WIDTH:", "WIDTH:",
"LAYER_CHANGE", "LAYER_CHANGE",
"LAYER_CHANGE_TRAVEL",
"COLOR_CHANGE", "COLOR_CHANGE",
"PAUSE_PRINT", "PAUSE_PRINT",
"CUSTOM_GCODE", "CUSTOM_GCODE",

View File

@ -192,6 +192,7 @@ namespace Slic3r {
Height, Height,
Width, Width,
Layer_Change, Layer_Change,
Layer_Change_Travel,
Color_Change, Color_Change,
Pause_Print, Pause_Print,
Custom_Code, Custom_Code,

View File

@ -275,10 +275,8 @@ std::string GCodeWriter::set_speed(double F, const std::string_view comment, con
return w.string(); return w.string();
} }
std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string_view comment) std::string GCodeWriter::get_travel_to_xy_gcode(const Vec2d &point, const std::string_view comment) const
{ {
m_pos.head<2>() = point.head<2>();
GCodeG1Formatter w; GCodeG1Formatter w;
w.emit_xy(point); w.emit_xy(point);
w.emit_f(this->config.travel_speed.value * 60.0); w.emit_f(this->config.travel_speed.value * 60.0);
@ -286,6 +284,12 @@ std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string_view
return w.string(); 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);
}
std::string GCodeWriter::travel_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, const std::string_view comment) std::string GCodeWriter::travel_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, const std::string_view comment)
{ {
assert(std::abs(point.x()) < 1200.); assert(std::abs(point.x()) < 1200.);
@ -303,35 +307,49 @@ std::string GCodeWriter::travel_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij
return w.string(); return w.string();
} }
std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string_view comment) std::string GCodeWriter::travel_to_xyz(const Vec3d& from, const Vec3d &to, const std::string_view comment)
{ {
if (std::abs(point.x() - m_pos.x()) < EPSILON && std::abs(point.y() - m_pos.y()) < EPSILON) { if (std::abs(to.x() - m_pos.x()) < EPSILON && std::abs(to.y() - m_pos.y()) < EPSILON) {
return this->travel_to_z(point.z(), comment); return this->travel_to_z(to.z(), comment);
} else if (std::abs(point.z() - m_pos.z()) < EPSILON) { } else if (std::abs(to.z() - m_pos.z()) < EPSILON) {
return this->travel_to_xy(point.head<2>(), comment); return this->travel_to_xy(to.head<2>(), comment);
} else { } else {
m_pos = point; m_pos = to;
return this->get_travel_to_xyz_gcode(from, to, comment);
}
}
std::string GCodeWriter::get_travel_to_xyz_gcode(const Vec3d &from, const Vec3d &to, const std::string_view comment) const {
GCodeG1Formatter w; GCodeG1Formatter w;
w.emit_xyz(point); w.emit_xyz(to);
const double distance_xy{(to.head<2>() - from.head<2>()).norm()};
const double distnace_z{std::abs(to.z() - from.z())};
const double time_z = distnace_z / this->config.travel_speed_z.value;
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((this->config.travel_speed.value * factor + (1 - factor) * this->config.travel_speed_z.value) * 60.0);
} else {
w.emit_f(this->config.travel_speed.value * 60.0);
}
Vec2f speed {this->config.travel_speed_z.value, this->config.travel_speed.value};
w.emit_f(speed.norm() * 60.0);
w.emit_comment(this->config.gcode_comments, comment); w.emit_comment(this->config.gcode_comments, comment);
return w.string(); 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)
{ {
return std::abs(m_pos.z() - z) < EPSILON ? "" : this->get_travel_to_z_gcode(z, comment); if (std::abs(m_pos.z() - z) < EPSILON) {
return "";
} else {
m_pos.z() = z;
return this->get_travel_to_z_gcode(z, comment);
}
} }
std::string GCodeWriter::get_travel_to_z_gcode(double z, const std::string_view comment) std::string GCodeWriter::get_travel_to_z_gcode(double z, const std::string_view comment) const
{ {
m_pos.z() = z;
double speed = this->config.travel_speed_z.value; double speed = this->config.travel_speed_z.value;
if (speed == 0.) if (speed == 0.)
speed = this->config.travel_speed.value; speed = this->config.travel_speed.value;

View File

@ -66,10 +66,23 @@ public:
std::string toolchange_prefix() const; std::string toolchange_prefix() const;
std::string toolchange(unsigned int extruder_id); 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 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(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 get_travel_to_z_gcode(double z, const std::string_view comment); /**
* @brief Return gcode with all three axis defined. Optionally adds feedrate.
*
* 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 &from, const Vec3d &to, const std::string_view comment) const;
std::string travel_to_xyz(const Vec3d &from, 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_z(double z, const std::string_view comment = {}); std::string travel_to_z(double z, 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(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);

View File

@ -1,53 +0,0 @@
#include "LayerChanges.hpp"
#include "libslic3r/ClipperUtils.hpp"
namespace Slic3r::GCode::Impl::LayerChanges {
Polygon generate_regular_polygon(
const Point &centroid, const Point &start_point, const unsigned points_count
) {
Points points;
points.reserve(points_count);
const double part_angle{2 * M_PI / points_count};
for (unsigned i = 0; i < points_count; ++i) {
const double current_angle{i * part_angle};
points.emplace_back(scaled(std::cos(current_angle)), scaled(std::sin(current_angle)));
}
Polygon regular_polygon{points};
const Vec2d current_vector{unscaled(regular_polygon.points.front())};
const Vec2d expected_vector{unscaled(start_point) - unscaled(centroid)};
const double current_scale = current_vector.norm();
const double expected_scale = expected_vector.norm();
regular_polygon.scale(expected_scale / current_scale);
regular_polygon.rotate(angle(current_vector, expected_vector));
regular_polygon.translate(centroid);
return regular_polygon;
}
Bed::Bed(const std::vector<Vec2d> &shape, const double padding)
: inner_offset(get_inner_offset(shape, padding)), centroid(unscaled(inner_offset.centroid())) {}
bool Bed::contains_within_padding(const Vec2d &point) const {
return inner_offset.contains(scaled(point));
}
Polygon Bed::get_inner_offset(const std::vector<Vec2d> &shape, const double padding) {
Points shape_scaled;
shape_scaled.reserve(shape.size());
using std::begin, std::end, std::back_inserter, std::transform;
transform(begin(shape), end(shape), back_inserter(shape_scaled), [](const Vec2d &point) {
return scaled(point);
});
const Polygons inner_offset{shrink({Polygon{shape_scaled}}, scaled(padding))};
if (inner_offset.empty()) {
return Polygon{};
}
return inner_offset.front();
}
} // namespace Slic3r::GCode::Impl::LayerChanges

View File

@ -1,52 +0,0 @@
/**
* @file
* @brief Utility functions for layer change gcode generation.
*/
#ifndef slic3r_GCode_LayerChanges_hpp_
#define slic3r_GCode_LayerChanges_hpp_
#include "libslic3r/Point.hpp"
#include "libslic3r/Polygon.hpp"
namespace Slic3r::GCode::Impl::LayerChanges {
/**
* Generates a regular polygon - all angles are the same (e.g. typical hexagon).
*
* @param centroid Central point.
* @param start_point The polygon point are ordered. This is the first point.
* @param points_count Amount of nodes of the polygon (e.g. 6 for haxagon).
*
* Distance between centroid and start point sets the scale of the polygon.
*/
Polygon generate_regular_polygon(
const Point &centroid, const Point &start_point, const unsigned points_count
);
/**
* @brief A representation of the bed shape with inner padding.
*
* Its purpose is to facilitate the bed boundary checking.
*/
class Bed
{
private:
Polygon inner_offset;
static Polygon get_inner_offset(const std::vector<Vec2d> &shape, const double padding);
public:
/**
* Bed shape with inner padding.
*/
Bed(const std::vector<Vec2d> &shape, const double padding);
Vec2d centroid;
/**
* Returns true if the point is within the bed shape including inner padding.
*/
bool contains_within_padding(const Vec2d &point) const;
};
} // namespace Slic3r::GCode::Impl::LayerChanges
#endif // slic3r_GCode_LayerChanges_hpp_

View File

@ -43,8 +43,8 @@ double ElevatedTravelFormula::operator()(const double distance_from_start) const
Points3 generate_flat_travel(tcb::span<const Point> xy_path, const float elevation) { Points3 generate_flat_travel(tcb::span<const Point> xy_path, const float elevation) {
Points3 result; Points3 result;
result.reserve(xy_path.size() - 1); result.reserve(xy_path.size());
for (const Point &point : xy_path.subspan(1)) { for (const Point &point : xy_path) {
result.emplace_back(point.x(), point.y(), scaled(elevation)); result.emplace_back(point.x(), point.y(), scaled(elevation));
} }
return result; return result;
@ -140,21 +140,6 @@ std::optional<double> get_first_crossed_line_distance(
return {}; 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;
}
struct SmoothingParams struct SmoothingParams
{ {
double blend_width{}; double blend_width{};
@ -236,7 +221,10 @@ ElevatedTravelParams get_elevated_traval_params(
} }
std::optional<double> obstacle_adjusted_slope_end{ std::optional<double> obstacle_adjusted_slope_end{
get_obstacle_adjusted_slope_end(xy_path.lines(), previous_layer_distancer)}; previous_layer_distancer ?
get_first_crossed_line_distance(xy_path.lines(), *previous_layer_distancer) :
std::nullopt
};
if (obstacle_adjusted_slope_end && obstacle_adjusted_slope_end < elevation_params.slope_end) { if (obstacle_adjusted_slope_end && obstacle_adjusted_slope_end < elevation_params.slope_end) {
elevation_params.slope_end = *obstacle_adjusted_slope_end; elevation_params.slope_end = *obstacle_adjusted_slope_end;
@ -252,11 +240,6 @@ ElevatedTravelParams get_elevated_traval_params(
return elevation_params; return elevation_params;
} }
/**
* @brief Generate regulary spaced points on 1 axis. Includes both from and to.
*
* If count is 1, the point is in the middle of the range.
*/
std::vector<double> linspace(const double from, const double to, const unsigned count) { std::vector<double> linspace(const double from, const double to, const unsigned count) {
if (count == 0) { if (count == 0) {
return {}; return {};

View File

@ -89,6 +89,20 @@ std::vector<DistancedPoint> slice_xy_path(
tcb::span<const Point> xy_path, tcb::span<const double> sorted_distances tcb::span<const Point> xy_path, tcb::span<const double> sorted_distances
); );
/**
* @brief Generate regulary spaced points on 1 axis. Includes both from and to.
*
* If count is 1, the point is in the middle of the range.
*/
std::vector<double> linspace(const double from, const double to, const unsigned count);
ElevatedTravelParams get_elevated_traval_params(
const Polyline& xy_path,
const FullPrintConfig &config,
const unsigned extruder_id,
const std::optional<AABBTreeLines::LinesDistancer<Linef>> &previous_layer_distancer = std::nullopt
);
/** /**
* @brief Simply return the xy_path with z coord set to elevation. * @brief Simply return the xy_path with z coord set to elevation.
*/ */

View File

@ -57,12 +57,24 @@ 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) {
const Point xy_point = wipe_tower_point_to_object_point(gcodegen, start_pos);
gcode += gcodegen.retract_and_wipe(); gcode += gcodegen.retract_and_wipe();
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once(); gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
if (gcodegen.m_current_layer_first_position) {
gcode += gcodegen.travel_to( gcode += gcodegen.travel_to(
wipe_tower_point_to_object_point(gcodegen, start_pos), xy_point,
ExtrusionRole::Mixed, ExtrusionRole::Mixed,
"Travel to a Wipe Tower"); "Travel to a Wipe Tower");
} else {
const Vec3crd point = to_3d(xy_point, scaled(z));
const Vec3d gcode_point = to_3d(gcodegen.point_to_gcode(point.head<2>()), z);
gcodegen.set_last_pos(point.head<2>());
gcodegen.writer().update_position(gcode_point);
gcode += gcodegen.writer().get_travel_to_xy_gcode(gcode_point.head<2>(), "move to first layer point");
gcode += gcodegen.writer().get_travel_to_z_gcode(gcode_point.z(), "move to first layer point");
gcodegen.m_current_layer_first_position = gcode_point;
}
gcode += gcodegen.unretract(); gcode += gcodegen.unretract();
} else { } else {
// When this is multiextruder printer without any ramming, we can just change // When this is multiextruder printer without any ramming, we can just change

View File

@ -14,7 +14,6 @@ add_executable(${_TEST_NAME}_tests
test_gaps.cpp test_gaps.cpp
test_gcode.cpp test_gcode.cpp
test_gcode_travels.cpp test_gcode_travels.cpp
test_gcode_layer_changes.cpp
test_gcodefindreplace.cpp test_gcodefindreplace.cpp
test_gcodewriter.cpp test_gcodewriter.cpp
test_layers.cpp test_layers.cpp

View File

@ -1,55 +0,0 @@
#include <catch2/catch.hpp>
#include <libslic3r/GCode/LayerChanges.hpp>
using namespace Slic3r;
using namespace Slic3r::GCode::Impl::LayerChanges;
TEST_CASE("Generate regular polygon", "[GCode]") {
const unsigned points_count{32};
const Point centroid{scaled(Vec2d{5, -2})};
const Polygon result{generate_regular_polygon(centroid, scaled(Vec2d{0, 0}), points_count)};
const Point oposite_point{centroid * 2};
REQUIRE(result.size() == 32);
CHECK(result[16].x() == Approx(oposite_point.x()));
CHECK(result[16].y() == Approx(oposite_point.y()));
std::vector<double> angles;
angles.reserve(points_count);
for (unsigned index = 0; index < points_count; index++) {
const unsigned previous_index{index == 0 ? points_count - 1 : index - 1};
const unsigned next_index{index == points_count - 1 ? 0 : index + 1};
const Point previous_point = result.points[previous_index];
const Point current_point = result.points[index];
const Point next_point = result.points[next_index];
angles.emplace_back(angle(Vec2crd{previous_point - current_point}, Vec2crd{next_point - current_point}));
}
std::vector<double> expected;
angles.reserve(points_count);
std::generate_n(std::back_inserter(expected), points_count, [&](){
return angles.front();
});
CHECK_THAT(angles, Catch::Matchers::Approx(expected));
}
TEST_CASE("Square bed with padding", "[GCode]") {
const Bed bed{
{
Vec2d{0, 0},
Vec2d{100, 0},
Vec2d{100, 100},
Vec2d{0, 100}
},
10.0
};
CHECK(bed.centroid.x() == 50);
CHECK(bed.centroid.y() == 50);
CHECK(bed.contains_within_padding(Vec2d{10, 10}));
CHECK_FALSE(bed.contains_within_padding(Vec2d{9, 10}));
}