Prusa 2.7.2

This commit is contained in:
sunsets
2024-03-27 14:38:03 +08:00
parent 63daf0c087
commit 2387bc9cdb
203 changed files with 6053 additions and 15634 deletions

View File

@@ -1173,7 +1173,7 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCodeGenerator &gcodegen, cons
// Otherwise perform the path planning in the coordinate system of the active object.
bool use_external = m_use_external_mp || m_use_external_mp_once;
Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0);
const Point start = gcodegen.last_pos() + scaled_origin;
const Point start = *gcodegen.last_position + scaled_origin;
const Point end = point + scaled_origin;
const Line travel(start, end);
@@ -1470,7 +1470,7 @@ static size_t avoid_perimeters(const AvoidCrossingPerimeters::Boundary &boundary
}
// Plan travel, which avoids perimeter crossings by following the boundaries of the layer.
Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled)
Polyline AvoidCrossingPerimeters::travel_to(const GCodeGenerator &gcodegen, const Point &point, bool *could_be_wipe_disabled)
{
// If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset).
// Otherwise perform the path planning in the coordinate system of the active object.

View File

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

View File

@@ -185,6 +185,9 @@ namespace Slic3r {
Height,
Width,
Layer_Change,
Layer_Change_Travel,
Layer_Change_Retraction_Start,
Layer_Change_Retraction_End,
Color_Change,
Pause_Print,
Custom_Code,

View File

@@ -316,9 +316,8 @@ std::string GCodeWriter::set_speed(double F, const std::string_view comment, con
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;
w.emit_xy(point);
@@ -330,6 +329,11 @@ std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string_view
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)
{
assert(std::abs(point.x()) < 1200.);
@@ -347,33 +351,53 @@ std::string GCodeWriter::travel_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij
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) {
return this->travel_to_z(point.z(), comment);
} else if (std::abs(point.z() - m_pos.z()) < EPSILON) {
return this->travel_to_xy(point.head<2>(), 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);
} 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;
w.emit_xyz(point);
Vec2f speed {this->config.travel_speed_z.value, this->config.travel_speed.value};
w.emit_f(speed.norm() * 60.0);
w.emit_xyz(to);
double speed_z = this->config.travel_speed_z.value;
if (speed_z == 0.)
speed_z = this->config.travel_speed.value;
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 / 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((this->config.travel_speed.value * factor + (1 - factor) * speed_z) * 60.0);
} else {
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)
{
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;
if (speed == 0.)

View File

@@ -66,10 +66,21 @@ public:
std::string toolchange_prefix() const;
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 = {});
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 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);
@@ -224,7 +235,8 @@ public:
}
void emit_string(const std::string_view s) {
strncpy(ptr_err.ptr, s.data(), s.size());
// Be aware that std::string_view::data() returns a pointer to a buffer that is not necessarily null-terminated.
memcpy(ptr_err.ptr, s.data(), s.size());
ptr_err.ptr += s.size();
}

View File

@@ -81,7 +81,8 @@ void LabelObjects::init(const Print& print)
if (object_has_more_instances)
name += " (Instance " + std::to_string(instance_id) + ")";
if (m_flavor == gcfKlipper) {
const std::string banned = "-. \r\n\v\t\f";
// Disallow Klipper special chars, common illegal filename chars, etc.
const std::string banned = "\b\t\n\v\f\r \"#%&\'*-./:;<>\\";
std::replace_if(name.begin(), name.end(), [&banned](char c) { return banned.find(c) != std::string::npos; }, '_');
}
}
@@ -111,7 +112,7 @@ std::string LabelObjects::all_objects_header() const
for (const auto& [print_instance, label] : label_data_sorted) {
if (m_label_objects_style == LabelObjectsStyle::Firmware && m_flavor == gcfKlipper) {
char buffer[64];
out += "EXCLUDE_OBJECT_DEFINE NAME=" + label.name;
out += "EXCLUDE_OBJECT_DEFINE NAME='" + label.name + "'";
Polygon outline = instance_outline(print_instance);
assert(! outline.empty());
outline.douglas_peucker(50000.f);
@@ -154,7 +155,7 @@ std::string LabelObjects::start_object(const PrintInstance& print_instance, Incl
}
out += "\n";
} else if (m_flavor == gcfKlipper)
out += "EXCLUDE_OBJECT_START NAME=" + label.name + "\n";
out += "EXCLUDE_OBJECT_START NAME='" + label.name + "'\n";
else {
// Not supported by / implemented for the other firmware flavors.
}
@@ -178,7 +179,7 @@ std::string LabelObjects::stop_object(const PrintInstance& print_instance) const
if (m_flavor == GCodeFlavor::gcfMarlinFirmware || m_flavor == GCodeFlavor::gcfMarlinLegacy || m_flavor == GCodeFlavor::gcfRepRapFirmware)
out += std::string("M486 S-1\n");
else if (m_flavor ==gcfKlipper)
out += "EXCLUDE_OBJECT_END NAME=" + label.name + "\n";
out += "EXCLUDE_OBJECT_END NAME='" + label.name + "'\n";
else {
// Not supported by / implemented for the other firmware flavors.
}

View File

@@ -105,7 +105,7 @@ ToolOrdering::ToolOrdering(const PrintObject &object, unsigned int first_extrude
double max_layer_height = calc_max_layer_height(object.print()->config(), object.config().layer_height);
// Collect extruders required to print the layers.
this->collect_extruders(object, std::vector<std::pair<double, unsigned int>>());
this->collect_extruders(object, std::vector<std::pair<double, unsigned int>>(), std::vector<std::pair<double, unsigned int>>());
// Reorder the extruders to minimize tool switches.
this->reorder_extruders(first_extruder);
@@ -151,17 +151,24 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool
// Use the extruder switches from Model::custom_gcode_per_print_z to override the extruder to print the object.
// Do it only if all the objects were configured to be printed with a single extruder.
std::vector<std::pair<double, unsigned int>> per_layer_extruder_switches;
if (auto num_extruders = unsigned(print.config().nozzle_diameter.size());
num_extruders > 1 && print.object_extruders().size() == 1 && // the current Print's configuration is CustomGCode::MultiAsSingle
auto num_extruders = unsigned(print.config().nozzle_diameter.size());
if (num_extruders > 1 && print.object_extruders().size() == 1 && // the current Print's configuration is CustomGCode::MultiAsSingle
print.model().custom_gcode_per_print_z.mode == CustomGCode::MultiAsSingle) {
// Printing a single extruder platter on a printer with more than 1 extruder (or single-extruder multi-material).
// There may be custom per-layer tool changes available at the model.
per_layer_extruder_switches = custom_tool_changes(print.model().custom_gcode_per_print_z, num_extruders);
}
// Collect extruders reuqired to print the layers.
// Color changes for each layer to determine which extruder needs to be picked before color change.
// This is done just for multi-extruder printers without enabled Single Extruder Multi Material (tool changer printers).
std::vector<std::pair<double, unsigned int>> per_layer_color_changes;
if (num_extruders > 1 && print.model().custom_gcode_per_print_z.mode == CustomGCode::MultiExtruder && !print.config().single_extruder_multi_material) {
per_layer_color_changes = custom_color_changes(print.model().custom_gcode_per_print_z, num_extruders);
}
// Collect extruders required to print the layers.
for (auto object : print.objects())
this->collect_extruders(*object, per_layer_extruder_switches);
this->collect_extruders(*object, per_layer_extruder_switches, per_layer_color_changes);
// Reorder the extruders to minimize tool switches.
this->reorder_extruders(first_extruder);
@@ -214,8 +221,11 @@ void ToolOrdering::initialize_layers(std::vector<coordf_t> &zs)
}
// Collect extruders reuqired to print layers.
void ToolOrdering::collect_extruders(const PrintObject &object, const std::vector<std::pair<double, unsigned int>> &per_layer_extruder_switches)
{
void ToolOrdering::collect_extruders(
const PrintObject &object,
const std::vector<std::pair<double, unsigned int>> &per_layer_extruder_switches,
const std::vector<std::pair<double, unsigned int>> &per_layer_color_changes
) {
// Collect the support extruders.
for (auto support_layer : object.support_layers()) {
LayerTools &layer_tools = this->tools_for_layer(support_layer->print_z);
@@ -233,10 +243,10 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
}
// Extruder overrides are ordered by print_z.
std::vector<std::pair<double, unsigned int>>::const_iterator it_per_layer_extruder_override;
it_per_layer_extruder_override = per_layer_extruder_switches.begin();
std::vector<std::pair<double, unsigned int>>::const_iterator it_per_layer_extruder_override = per_layer_extruder_switches.begin();
unsigned int extruder_override = 0;
std::vector<std::pair<double, unsigned int>>::const_iterator it_per_layer_color_changes = per_layer_color_changes.begin();
// Collect the object extruders.
for (auto layer : object.layers()) {
LayerTools &layer_tools = this->tools_for_layer(layer->print_z);
@@ -248,6 +258,14 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
// Store the current extruder override (set to zero if no overriden), so that layer_tools.wiping_extrusions().is_overridable_and_mark() will use it.
layer_tools.extruder_override = extruder_override;
// Append the extruder needed to be picked before performing the color change.
for (; it_per_layer_color_changes != per_layer_color_changes.end() && it_per_layer_color_changes->first < layer->print_z + EPSILON; ++it_per_layer_color_changes) {
if (std::abs(it_per_layer_color_changes->first - layer->print_z) < EPSILON) {
assert(layer_tools.extruder_needed_for_color_changer == 0); // Just on color change per layer is allowed.
layer_tools.extruder_needed_for_color_changer = it_per_layer_color_changes->second;
layer_tools.extruders.emplace_back(it_per_layer_color_changes->second);
}
}
// What extruders are required to print this object layer?
for (const LayerRegion *layerm : layer->regions()) {
const PrintRegion &region = layerm->region();
@@ -365,6 +383,11 @@ void ToolOrdering::reorder_extruders(unsigned int last_extruder_id)
std::swap(lt.extruders[i], lt.extruders.front());
break;
}
} else if (lt.extruder_needed_for_color_changer != 0) {
// Put the extruder needed for performing the color change at the beginning.
auto it = std::find(lt.extruders.begin(), lt.extruders.end(), lt.extruder_needed_for_color_changer);
assert(it != lt.extruders.end());
std::rotate(lt.extruders.begin(), it, it + 1);
}
}
last_extruder_id = lt.extruders.back();

View File

@@ -94,6 +94,9 @@ public:
// If per layer extruder switches are inserted by the G-code preview slider, this value contains the new (1 based) extruder, with which the whole object layer is being printed with.
// If not overriden, it is set to 0.
unsigned int extruder_override = 0;
// For multi-extruder printers, when there is a color change, this contains an extruder (1 based) on which the color change will be performed.
// Otherwise, it is set to 0.
unsigned int extruder_needed_for_color_changer = 0;
// Should a skirt be printed at this layer?
// Layers are marked for infinite skirt aka draft shield. Not all the layers have to be printed.
bool has_skirt = false;
@@ -165,7 +168,7 @@ public:
private:
void initialize_layers(std::vector<coordf_t> &zs);
void collect_extruders(const PrintObject &object, const std::vector<std::pair<double, unsigned int>> &per_layer_extruder_switches);
void collect_extruders(const PrintObject &object, const std::vector<std::pair<double, unsigned int>> &per_layer_extruder_switches, const std::vector<std::pair<double, unsigned int>> &per_layer_color_changes);
void reorder_extruders(unsigned int last_extruder_id);
void fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z, coordf_t max_layer_height);
bool insert_wipe_tower_extruder();

View File

@@ -0,0 +1,444 @@
#include "Travels.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/Layer.hpp"
#include "libslic3r/Print.hpp"
#include "../GCode.hpp"
namespace Slic3r::GCode {
static Lines extrusion_entity_to_lines(const ExtrusionEntity &e_entity)
{
if (const auto *path = dynamic_cast<const ExtrusionPath *>(&e_entity)) {
return to_lines(path->as_polyline());
} else if (const auto *multipath = dynamic_cast<const ExtrusionMultiPath *>(&e_entity)) {
return to_lines(multipath->as_polyline());
} else if (const auto *loop = dynamic_cast<const ExtrusionLoop *>(&e_entity)) {
return to_lines(loop->polygon());
} else {
throw Slic3r::InvalidArgument("Invalid argument supplied to TODO()");
}
return {};
}
AABBTreeLines::LinesDistancer<ObjectOrExtrusionLinef> get_previous_layer_distancer(
const GCodeGenerator::ObjectsLayerToPrint &objects_to_print, const ExPolygons &slices
) {
std::vector<ObjectOrExtrusionLinef> lines;
for (const GCodeGenerator::ObjectLayerToPrint &object_to_print : objects_to_print) {
if (const PrintObject *object = object_to_print.object(); object) {
const size_t object_layer_idx = &object_to_print - &objects_to_print.front();
for (const PrintInstance &instance : object->instances()) {
const size_t instance_idx = &instance - &object->instances().front();
for (const ExPolygon &polygon : slices)
for (const Line &line : polygon.lines())
lines.emplace_back(unscaled(Point{line.a + instance.shift}), unscaled(Point{line.b + instance.shift}), object_layer_idx, instance_idx);
}
}
}
return AABBTreeLines::LinesDistancer{std::move(lines)};
}
std::pair<AABBTreeLines::LinesDistancer<ObjectOrExtrusionLinef>, size_t> get_current_layer_distancer(const ObjectsLayerToPrint &objects_to_print)
{
std::vector<ObjectOrExtrusionLinef> lines;
size_t extrusion_entity_cnt = 0;
for (const ObjectLayerToPrint &object_to_print : objects_to_print) {
const size_t object_layer_idx = &object_to_print - &objects_to_print.front();
if (const Layer *layer = object_to_print.object_layer; layer) {
for (const PrintInstance &instance : layer->object()->instances()) {
const size_t instance_idx = &instance - &layer->object()->instances().front();
for (const LayerSlice &lslice : layer->lslices_ex) {
for (const LayerIsland &island : lslice.islands) {
const LayerRegion &layerm = *layer->get_region(island.perimeters.region());
for (uint32_t perimeter_id : island.perimeters) {
assert(dynamic_cast<const ExtrusionEntityCollection *>(layerm.perimeters().entities[perimeter_id]));
const auto *eec = static_cast<const ExtrusionEntityCollection *>(layerm.perimeters().entities[perimeter_id]);
for (const ExtrusionEntity *ee : *eec) {
if (ee->role().is_external_perimeter()) {
for (const Line &line : extrusion_entity_to_lines(*ee))
lines.emplace_back(unscaled(Point{line.a + instance.shift}), unscaled(Point{line.b + instance.shift}), object_layer_idx, instance_idx, ee);
}
++extrusion_entity_cnt;
}
}
}
}
}
}
}
return {AABBTreeLines::LinesDistancer{std::move(lines)}, extrusion_entity_cnt};
}
void TravelObstacleTracker::init_layer(const Layer &layer, const ObjectsLayerToPrint &objects_to_print)
{
size_t extrusion_entity_cnt = 0;
m_extruded_extrusion.clear();
m_objects_to_print = objects_to_print;
m_previous_layer_distancer = get_previous_layer_distancer(m_objects_to_print, layer.lower_layer->lslices);
std::tie(m_current_layer_distancer, extrusion_entity_cnt) = get_current_layer_distancer(m_objects_to_print);
m_extruded_extrusion.reserve(extrusion_entity_cnt);
}
void TravelObstacleTracker::mark_extruded(const ExtrusionEntity *extrusion_entity, size_t object_layer_idx, size_t instance_idx)
{
if (extrusion_entity->role().is_external_perimeter())
this->m_extruded_extrusion.insert({int(object_layer_idx), int(instance_idx), extrusion_entity});
}
bool TravelObstacleTracker::is_extruded(const ObjectOrExtrusionLinef &line) const
{
return m_extruded_extrusion.find({line.object_layer_idx, line.instance_idx, line.extrusion_entity}) != m_extruded_extrusion.end();
}
} // namespace Slic3r::GCode
namespace Slic3r::GCode::Impl::Travels {
ElevatedTravelFormula::ElevatedTravelFormula(const ElevatedTravelParams &params)
: smoothing_from(params.slope_end - params.blend_width / 2.0)
, smoothing_to(params.slope_end + params.blend_width / 2.0)
, blend_width(params.blend_width)
, lift_height(params.lift_height)
, slope_end(params.slope_end) {
if (smoothing_from < 0) {
smoothing_from = params.slope_end;
smoothing_to = params.slope_end;
}
}
double parabola(const double x, const double a, const double b, const double c) {
return a * x * x + b * x + c;
}
double ElevatedTravelFormula::slope_function(double distance_from_start) const {
if (distance_from_start < this->slope_end) {
const double lift_percent = distance_from_start / this->slope_end;
return lift_percent * this->lift_height;
} else {
return this->lift_height;
}
}
double ElevatedTravelFormula::operator()(const double distance_from_start) const {
if (distance_from_start > this->smoothing_from && distance_from_start < this->smoothing_to) {
const double slope = this->lift_height / this->slope_end;
// This is a part of a parabola going over a specific
// range and with specific end slopes.
const double a = -slope / 2.0 / this->blend_width;
const double b = slope * this->smoothing_to / this->blend_width;
const double c = this->lift_height + a * boost::math::pow<2>(this->smoothing_to);
return parabola(distance_from_start, a, b, c);
}
return slope_function(distance_from_start);
}
Points3 generate_flat_travel(tcb::span<const Point> xy_path, const float elevation) {
Points3 result;
result.reserve(xy_path.size());
for (const Point &point : xy_path) {
result.emplace_back(point.x(), point.y(), scaled(elevation));
}
return result;
}
Vec2d place_at_segment(
const Vec2d &current_point, const Vec2d &previous_point, const double distance
) {
Vec2d direction = (current_point - previous_point).normalized();
return previous_point + direction * distance;
}
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;
}
Points3 generate_elevated_travel(
const tcb::span<const Point> xy_path,
const std::vector<double> &ensure_points_at_distances,
const double initial_elevation,
const std::function<double(double)> &elevation
) {
Points3 result{};
std::vector<DistancedPoint> extended_xy_path = slice_xy_path(xy_path, ensure_points_at_distances);
result.reserve(extended_xy_path.size());
for (const DistancedPoint &point : extended_xy_path) {
result.emplace_back(
point.point.x(), point.point.y(),
scaled(initial_elevation + elevation(point.distance_from_start))
);
}
return result;
}
struct Intersection
{
int object_layer_idx = -1;
int instance_idx = -1;
bool is_inside = false;
bool is_print_instance_equal(const ObjectOrExtrusionLinef &print_istance) {
return this->object_layer_idx == print_istance.object_layer_idx && this->instance_idx == print_istance.instance_idx;
}
};
double get_first_crossed_line_distance(
tcb::span<const Line> xy_path,
const AABBTreeLines::LinesDistancer<ObjectOrExtrusionLinef> &distancer,
const ObjectsLayerToPrint &objects_to_print,
const std::function<bool(const ObjectOrExtrusionLinef &)> &predicate,
const bool ignore_starting_object_intersection
) {
assert(!xy_path.empty());
if (xy_path.empty())
return std::numeric_limits<double>::max();
const Point path_first_point = xy_path.front().a;
double traversed_distance = 0;
bool skip_intersection = ignore_starting_object_intersection;
Intersection first_intersection;
for (const Line &line : xy_path) {
const ObjectOrExtrusionLinef unscaled_line = {unscaled(line.a), unscaled(line.b)};
const std::vector<std::pair<Vec2d, size_t>> intersections = distancer.intersections_with_line<true>(unscaled_line);
if (intersections.empty())
continue;
if (!objects_to_print.empty() && ignore_starting_object_intersection && first_intersection.object_layer_idx == -1) {
const ObjectOrExtrusionLinef &intersection_line = distancer.get_line(intersections.front().second);
const Point shift = objects_to_print[intersection_line.object_layer_idx].layer()->object()->instances()[intersection_line.instance_idx].shift;
const Point shifted_first_point = path_first_point - shift;
const bool contain_first_point = expolygons_contain(objects_to_print[intersection_line.object_layer_idx].layer()->lslices, shifted_first_point);
first_intersection = {intersection_line.object_layer_idx, intersection_line.instance_idx, contain_first_point};
}
for (const auto &intersection : intersections) {
const ObjectOrExtrusionLinef &intersection_line = distancer.get_line(intersection.second);
const double distance = traversed_distance + (unscaled_line.a - intersection.first).norm();
if (distance <= EPSILON)
continue;
// There is only one external border for each object, so when we cross this border,
// we definitely know that we are outside the object.
if (skip_intersection && first_intersection.is_print_instance_equal(intersection_line) && first_intersection.is_inside) {
skip_intersection = false;
continue;
}
if (!predicate(intersection_line))
continue;
return distance;
}
traversed_distance += (unscaled_line.a - unscaled_line.b).norm();
}
return std::numeric_limits<double>::max();
}
double get_obstacle_adjusted_slope_end(const Lines &xy_path, const GCode::TravelObstacleTracker &obstacle_tracker) {
const double previous_layer_crossed_line = get_first_crossed_line_distance(
xy_path, obstacle_tracker.previous_layer_distancer(), obstacle_tracker.objects_to_print()
);
const double current_layer_crossed_line = get_first_crossed_line_distance(
xy_path, obstacle_tracker.current_layer_distancer(), obstacle_tracker.objects_to_print(),
[&obstacle_tracker](const ObjectOrExtrusionLinef &line) { return obstacle_tracker.is_extruded(line); }
);
return std::min(previous_layer_crossed_line, current_layer_crossed_line);
}
struct SmoothingParams
{
double blend_width{};
unsigned points_count{1};
};
SmoothingParams get_smoothing_params(
const double lift_height,
const double slope_end,
unsigned extruder_id,
const double travel_length,
const FullPrintConfig &config
) {
if (config.gcode_flavor != gcfMarlinFirmware)
// Smoothing is supported only on Marlin.
return {0, 1};
const double slope = lift_height / slope_end;
const double max_machine_z_velocity = config.machine_max_feedrate_z.get_at(extruder_id);
const double max_xy_velocity =
Vec2d{
config.machine_max_feedrate_x.get_at(extruder_id),
config.machine_max_feedrate_y.get_at(extruder_id)}
.norm();
const double xy_acceleration = config.machine_max_acceleration_travel.get_at(extruder_id);
const double xy_acceleration_time = max_xy_velocity / xy_acceleration;
const double xy_acceleration_distance = 1.0 / 2.0 * xy_acceleration *
boost::math::pow<2>(xy_acceleration_time);
if (travel_length < xy_acceleration_distance) {
return {0, 1};
}
const double max_z_velocity = std::min(max_xy_velocity * slope, max_machine_z_velocity);
const double deceleration_time = max_z_velocity /
config.machine_max_acceleration_z.get_at(extruder_id);
const double deceleration_xy_distance = deceleration_time * max_xy_velocity;
const double blend_width = slope_end > deceleration_xy_distance / 2.0 ? deceleration_xy_distance :
slope_end * 2.0;
const unsigned points_count = blend_width > 0 ?
std::ceil(max_z_velocity / config.machine_max_jerk_z.get_at(extruder_id)) :
1;
if (blend_width <= 0 // When there is no blend with, there is no need for smoothing.
|| points_count > 6 // That would be way to many points. Do not do it at all.
|| points_count <= 0 // Always return at least one point.
)
return {0, 1};
return {blend_width, points_count};
}
ElevatedTravelParams get_elevated_traval_params(
const Polyline& xy_path,
const FullPrintConfig &config,
const unsigned extruder_id,
const GCode::TravelObstacleTracker &obstacle_tracker
) {
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);
elevation_params.blend_width = 0;
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);
}
const double obstacle_adjusted_slope_end = get_obstacle_adjusted_slope_end(xy_path.lines(), obstacle_tracker);
if (obstacle_adjusted_slope_end < elevation_params.slope_end)
elevation_params.slope_end = obstacle_adjusted_slope_end;
SmoothingParams smoothing_params{get_smoothing_params(
elevation_params.lift_height, elevation_params.slope_end, extruder_id,
unscaled(xy_path.length()), config
)};
elevation_params.blend_width = smoothing_params.blend_width;
elevation_params.parabola_points_count = smoothing_params.points_count;
return elevation_params;
}
std::vector<double> linspace(const double from, const double to, const unsigned count) {
if (count == 0) {
return {};
}
std::vector<double> result;
result.reserve(count);
if (count == 1) {
result.emplace_back((from + to) / 2.0);
return result;
}
const double step = (to - from) / count;
for (unsigned i = 0; i < count - 1; ++i) {
result.emplace_back(from + i * step);
}
result.emplace_back(to); // Make sure the last value is exactly equal to the value of "to".
return result;
}
Points3 generate_travel_to_extrusion(
const Polyline &xy_path,
const FullPrintConfig &config,
const unsigned extruder_id,
const double initial_elevation,
const GCode::TravelObstacleTracker &obstacle_tracker,
const Point &xy_path_coord_origin
) {
const double upper_limit = config.retract_lift_below.get_at(extruder_id);
const double lower_limit = config.retract_lift_above.get_at(extruder_id);
if ((lower_limit > 0 && initial_elevation < lower_limit) ||
(upper_limit > 0 && initial_elevation > upper_limit)) {
return generate_flat_travel(xy_path.points, initial_elevation);
}
Points global_xy_path;
for (const Point &point : xy_path.points) {
global_xy_path.emplace_back(point + xy_path_coord_origin);
}
ElevatedTravelParams elevation_params{get_elevated_traval_params(
Polyline{std::move(global_xy_path)}, config, extruder_id, obstacle_tracker
)};
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 result{generate_elevated_travel(
xy_path.points, ensure_points_at_distances, initial_elevation,
ElevatedTravelFormula{elevation_params}
)};
result.emplace_back(xy_path.back().x(), xy_path.back().y(), scaled(initial_elevation));
return result;
}
} // namespace Slic3r::GCode::Impl::Travels

View File

@@ -0,0 +1,240 @@
/**
* @file
* @brief Utility functions for travel gcode generation.
*/
#ifndef slic3r_GCode_Travels_hpp_
#define slic3r_GCode_Travels_hpp_
#include <vector>
#include <tcbspan/span.hpp>
#include <functional>
#include <optional>
#include <boost/functional/hash.hpp>
#include <boost/math/special_functions/pow.hpp>
#include "libslic3r/AABBTreeLines.hpp"
// Forward declarations.
namespace Slic3r {
class Layer;
class Point;
class Linef;
class Polyline;
class FullPrintConfig;
class ExtrusionEntity;
} // namespace Slic3r
namespace Slic3r::GCode {
struct ObjectLayerToPrint;
using ObjectsLayerToPrint = std::vector<ObjectLayerToPrint>;
class ObjectOrExtrusionLinef : public Linef
{
public:
ObjectOrExtrusionLinef() = delete;
ObjectOrExtrusionLinef(const Vec2d &a, const Vec2d &b) : Linef(a, b) {}
explicit ObjectOrExtrusionLinef(const Vec2d &a, const Vec2d &b, size_t object_layer_idx, size_t instance_idx)
: Linef(a, b), object_layer_idx(int(object_layer_idx)), instance_idx(int(instance_idx)) {}
ObjectOrExtrusionLinef(const Vec2d &a, const Vec2d &b, size_t object_layer_idx, size_t instance_idx, const ExtrusionEntity *extrusion_entity)
: Linef(a, b), object_layer_idx(int(object_layer_idx)), instance_idx(int(instance_idx)), extrusion_entity(extrusion_entity) {}
virtual ~ObjectOrExtrusionLinef() = default;
const int object_layer_idx = -1;
const int instance_idx = -1;
const ExtrusionEntity *extrusion_entity = nullptr;
};
struct ExtrudedExtrusionEntity
{
const int object_layer_idx = -1;
const int instance_idx = -1;
const ExtrusionEntity *extrusion_entity = nullptr;
bool operator==(const ExtrudedExtrusionEntity &other) const
{
return extrusion_entity == other.extrusion_entity && object_layer_idx == other.object_layer_idx &&
instance_idx == other.instance_idx;
}
};
struct ExtrudedExtrusionEntityHash
{
size_t operator()(const ExtrudedExtrusionEntity &eee) const noexcept
{
std::size_t seed = std::hash<const ExtrusionEntity *>{}(eee.extrusion_entity);
boost::hash_combine(seed, std::hash<int>{}(eee.object_layer_idx));
boost::hash_combine(seed, std::hash<int>{}(eee.instance_idx));
return seed;
}
};
class TravelObstacleTracker
{
public:
void init_layer(const Layer &layer, const ObjectsLayerToPrint &objects_to_print);
void mark_extruded(const ExtrusionEntity *extrusion_entity, size_t object_layer_idx, size_t instance_idx);
bool is_extruded(const ObjectOrExtrusionLinef &line) const;
const AABBTreeLines::LinesDistancer<ObjectOrExtrusionLinef> &previous_layer_distancer() const { return m_previous_layer_distancer; }
const AABBTreeLines::LinesDistancer<ObjectOrExtrusionLinef> &current_layer_distancer() const { return m_current_layer_distancer; }
const ObjectsLayerToPrint &objects_to_print() const { return m_objects_to_print; }
private:
ObjectsLayerToPrint m_objects_to_print;
AABBTreeLines::LinesDistancer<ObjectOrExtrusionLinef> m_previous_layer_distancer;
AABBTreeLines::LinesDistancer<ObjectOrExtrusionLinef> m_current_layer_distancer;
std::unordered_set<ExtrudedExtrusionEntity, ExtrudedExtrusionEntityHash> m_extruded_extrusion;
};
} // namespace Slic3r::GCode
namespace Slic3r::GCode::Impl::Travels {
/**
* @brief A point on a curve with a distance from start.
*/
struct DistancedPoint
{
Point point;
double distance_from_start;
};
struct ElevatedTravelParams
{
/** Maximal value of nozzle lift. */
double lift_height{};
/** Distance from travel to the middle of the smoothing parabola. */
double slope_end{};
/** Width of the smoothing parabola */
double blend_width{};
/** How many points should be used to approximate the parabola */
unsigned parabola_points_count{};
};
/**
* @brief A mathematical formula for a smooth function.
*
* It starts lineary increasing than there is a parabola part and
* at the end it is flat.
*/
struct ElevatedTravelFormula
{
ElevatedTravelFormula(const ElevatedTravelParams &params);
double operator()(const double distance_from_start) const;
private:
double slope_function(double distance_from_start) const;
double smoothing_from;
double smoothing_to;
double blend_width;
double lift_height;
double slope_end;
};
/**
* @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 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 GCode::TravelObstacleTracker &obstacle_tracker
);
/**
* @brief Simply return the xy_path with z coord set to elevation.
*/
Points3 generate_flat_travel(tcb::span<const Point> xy_path, const float elevation);
/**
* @brief Take xy_path and genrate a travel acording to elevation.
*
* @param xy_path A list of points describing a path in xy.
* @param ensure_points_at_distances See slice_xy_path sorted_distances.
* @param elevation A function taking current distance in mm as input and returning elevation in mm
* as output.
*
* **Be aweare** that the elevation function operates in mm, while xy_path and returned travel are
* in scaled coordinates.
*/
Points3 generate_elevated_travel(
const tcb::span<const Point> xy_path,
const std::vector<double> &ensure_points_at_distances,
const double initial_elevation,
const std::function<double(double)> &elevation
);
/**
* @brief Given a AABB tree over lines find intersection with xy_path closest to the xy_path start.
*
* @param xy_path A path in 2D.
* @param distancer AABB Tree over lines.
* @param objects_to_print Objects to print are used to determine in which object xy_path starts.
* @param ignore_starting_object_intersection When it is true, then the first intersection during traveling from the object out is ignored.
* @return Distance to the first intersection if there is one.
*
* **Ignores intersection with xy_path starting point.**
*/
double get_first_crossed_line_distance(
tcb::span<const Line> xy_path,
const AABBTreeLines::LinesDistancer<ObjectOrExtrusionLinef> &distancer,
const ObjectsLayerToPrint &objects_to_print = {},
const std::function<bool(const ObjectOrExtrusionLinef &)> &predicate = [](const ObjectOrExtrusionLinef &) { return true; },
bool ignore_starting_object_intersection = true);
/**
* @brief Extract parameters and decide wheather the travel can be elevated.
* Then generate the whole travel 3D path - elevated if possible.
*/
Points3 generate_travel_to_extrusion(
const Polyline &xy_path,
const FullPrintConfig &config,
const unsigned extruder_id,
const double initial_elevation,
const GCode::TravelObstacleTracker &obstacle_tracker,
const Point &xy_path_coord_origin
);
} // namespace Slic3r::GCode::Impl::Travels
#endif // slic3r_GCode_Travels_hpp_

View File

@@ -21,9 +21,9 @@ void Wipe::init(const PrintConfig &config, const std::vector<unsigned int> &extr
if (config.wipe.get_at(id)) {
// Wipe length to extrusion ratio.
const double xy_to_e = this->calc_xy_to_e_ratio(config, id);
wipe_xy = std::max(wipe_xy, xy_to_e * config.retract_length.get_at(id));
wipe_xy = std::max(wipe_xy, config.retract_length.get_at(id) / xy_to_e);
if (multimaterial)
wipe_xy = std::max(wipe_xy, xy_to_e * config.retract_length_toolchange.get_at(id));
wipe_xy = std::max(wipe_xy, config.retract_length_toolchange.get_at(id) / xy_to_e);
}
if (wipe_xy == 0)
@@ -37,11 +37,11 @@ void Wipe::set_path(SmoothPath &&path, bool reversed)
this->reset_path();
if (this->enabled() && ! path.empty()) {
if (reversed) {
if (coord_t wipe_len_max_scaled = scaled(m_wipe_len_max); reversed) {
m_path = std::move(path.back().path);
Geometry::ArcWelder::reverse(m_path);
int64_t len = Geometry::ArcWelder::estimate_path_length(m_path);
for (auto it = std::next(path.rbegin()); len < m_wipe_len_max && it != path.rend(); ++ it) {
for (auto it = std::next(path.rbegin()); len < wipe_len_max_scaled && it != path.rend(); ++ it) {
if (it->path_attributes.role.is_bridge())
break; // Do not perform a wipe on bridges.
assert(it->path.size() >= 2);
@@ -55,7 +55,7 @@ void Wipe::set_path(SmoothPath &&path, bool reversed)
} else {
m_path = std::move(path.front().path);
int64_t len = Geometry::ArcWelder::estimate_path_length(m_path);
for (auto it = std::next(path.begin()); len < m_wipe_len_max && it != path.end(); ++ it) {
for (auto it = std::next(path.begin()); len < wipe_len_max_scaled && it != path.end(); ++ it) {
if (it->path_attributes.role.is_bridge())
break; // Do not perform a wipe on bridges.
assert(it->path.size() >= 2);
@@ -185,7 +185,7 @@ std::string Wipe::wipe(GCodeGenerator &gcodegen, bool toolchange)
return done;
};
// Start with the current position, which may be different from the wipe path start in case of loop clipping.
Vec2d prev = gcodegen.point_to_gcode_quantized(gcodegen.last_pos());
Vec2d prev = gcodegen.point_to_gcode_quantized(*gcodegen.last_position);
auto it = this->path().begin();
Vec2d p = gcodegen.point_to_gcode(it->point + m_offset);
++ it;
@@ -216,7 +216,7 @@ std::string Wipe::wipe(GCodeGenerator &gcodegen, bool toolchange)
// add tag for processor
assert(p == GCodeFormatter::quantize(p));
gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_End) + "\n";
gcodegen.set_last_pos(gcodegen.gcode_to_point(p));
gcodegen.last_position = gcodegen.gcode_to_point(p);
}
}

View File

@@ -530,7 +530,6 @@ WipeTower::WipeTower(const PrintConfig& config, const PrintRegionConfig& default
m_no_sparse_layers(config.wipe_tower_no_sparse_layers),
m_gcode_flavor(config.gcode_flavor),
m_travel_speed(config.travel_speed),
m_travel_speed_z(config.travel_speed_z),
m_infill_speed(default_region_config.infill_speed),
m_perimeter_speed(default_region_config.perimeter_speed),
m_current_tool(initial_tool),
@@ -1560,8 +1559,9 @@ void WipeTower::generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &
}
}
for (auto& used : m_used_filament_length) // reset used filament stats
used = 0.f;
m_used_filament_length.assign(m_used_filament_length.size(), 0.f); // reset used filament stats
assert(m_used_filament_length_until_layer.empty());
m_used_filament_length_until_layer.emplace_back(0.f, m_used_filament_length);
m_old_temperature = -1; // reset last temperature written in the gcode
@@ -1604,6 +1604,9 @@ void WipeTower::generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &
}
result.emplace_back(std::move(layer_result));
if (m_used_filament_length_until_layer.empty() || m_used_filament_length_until_layer.back().first != layer.z)
m_used_filament_length_until_layer.emplace_back();
m_used_filament_length_until_layer.back() = std::make_pair(layer.z, m_used_filament_length);
}
}

View File

@@ -219,7 +219,7 @@ public:
return m_current_layer_finished;
}
std::vector<float> get_used_filament() const { return m_used_filament_length; }
std::vector<std::pair<float, std::vector<float>>> get_used_filament_until_layer() const { return m_used_filament_length_until_layer; }
int get_number_of_toolchanges() const { return m_num_tool_changes; }
struct FilamentParameters {
@@ -275,7 +275,6 @@ private:
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)
float m_travel_speed = 0.f;
float m_travel_speed_z = 0.f;
float m_infill_speed = 0.f;
float m_perimeter_speed = 0.f;
float m_first_layer_speed = 0.f;
@@ -380,6 +379,7 @@ private:
// Stores information about used filament length per extruder:
std::vector<float> m_used_filament_length;
std::vector<std::pair<float, std::vector<float>>> m_used_filament_length_until_layer;
// Return index of first toolchange that switches to non-soluble extruder
// ot -1 if there is no such toolchange.

View File

@@ -57,12 +57,23 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip
|| is_ramming
|| will_go_down); // don't dig into the print
if (should_travel_to_tower) {
const Point xy_point = wipe_tower_point_to_object_point(gcodegen, start_pos);
gcode += gcodegen.retract_and_wipe();
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
const std::string comment{"Travel to a Wipe Tower"};
if (gcodegen.m_current_layer_first_position) {
if (gcodegen.last_position) {
gcode += gcodegen.travel_to(
wipe_tower_point_to_object_point(gcodegen, start_pos),
ExtrusionRole::Mixed,
"Travel to a Wipe Tower");
*gcodegen.last_position, xy_point, ExtrusionRole::Mixed, comment
);
} else {
gcode += gcodegen.writer().travel_to_xy(gcodegen.point_to_gcode(xy_point), comment);
gcode += gcodegen.writer().get_travel_to_z_gcode(z, comment);
}
} else {
const Vec3crd point = to_3d(xy_point, scaled(z));
gcode += gcodegen.travel_to_first_position(point, current_z);
}
gcode += gcodegen.unretract();
} else {
// When this is multiextruder printer without any ramming, we can just change
@@ -81,10 +92,16 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip
if (is_ramming)
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");
if (gcodegen.config().wipe_tower) {
if (tcr.priming) {
const double return_to_z{tcr.print_z + gcodegen.config().z_offset.value};
deretraction_str += gcodegen.writer().get_travel_to_z_gcode(return_to_z, "set priming layer Z");
} else {
deretraction_str += gcodegen.writer().travel_to_z(z, "restore layer Z");
}
deretraction_str += gcodegen.unretract();
}
}
assert(toolchange_gcode_str.empty() || toolchange_gcode_str.back() == '\n');
assert(deretraction_str.empty() || deretraction_str.back() == '\n');
@@ -98,7 +115,7 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip
// A phony move to the end position at the wipe tower.
gcodegen.writer().travel_to_xy(end_pos.cast<double>());
gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos));
gcodegen.last_position = wipe_tower_point_to_object_point(gcodegen, end_pos);
if (!is_approx(z, current_z)) {
gcode += gcodegen.writer().retract();
gcode += gcodegen.writer().travel_to_z(current_z, "Travel back up to the topmost object layer.");
@@ -247,7 +264,7 @@ std::string WipeTowerIntegration::finalize(GCodeGenerator &gcodegen)
std::string gcode;
if (std::abs(gcodegen.writer().get_position().z() - m_final_purge.print_z) > EPSILON)
gcode += gcodegen.generate_travel_gcode(
{{gcodegen.last_pos().x(), gcodegen.last_pos().y(), scaled(m_final_purge.print_z)}},
{{gcodegen.last_position->x(), gcodegen.last_position->y(), scaled(m_final_purge.print_z)}},
"move to safe place for purging"
);
gcode += append_tcr(gcodegen, m_final_purge, -1);

View File

@@ -26,7 +26,8 @@ public:
m_tool_changes(tool_changes),
m_final_purge(final_purge),
m_layer_idx(-1),
m_tool_change_idx(0)
m_tool_change_idx(0),
m_last_wipe_tower_print_z(print_config.z_offset.value)
{}
std::string prime(GCodeGenerator &gcodegen);
@@ -56,7 +57,7 @@ private:
// Current layer index.
int m_layer_idx;
int m_tool_change_idx;
double m_last_wipe_tower_print_z = 0.f;
double m_last_wipe_tower_print_z;
};
} // namespace GCode