mirror of
https://github.com/QIDITECH/QIDISlicer.git
synced 2026-02-02 17:08:42 +03:00
Prusa 2.7.2
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
}
|
||||
|
||||
@@ -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 ®ion = 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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
444
src/libslic3r/GCode/Travels.cpp
Normal file
444
src/libslic3r/GCode/Travels.cpp
Normal 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 ¶ms)
|
||||
: 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 ¤t_point, const Vec2d &previous_point, const double distance
|
||||
) {
|
||||
Vec2d direction = (current_point - previous_point).normalized();
|
||||
return previous_point + direction * distance;
|
||||
}
|
||||
|
||||
std::vector<DistancedPoint> slice_xy_path(
|
||||
tcb::span<const Point> xy_path, tcb::span<const double> sorted_distances
|
||||
) {
|
||||
assert(xy_path.size() >= 2);
|
||||
std::vector<DistancedPoint> result;
|
||||
result.reserve(xy_path.size() + sorted_distances.size());
|
||||
double total_distance{0};
|
||||
result.emplace_back(DistancedPoint{xy_path.front(), 0});
|
||||
Point previous_point = result.front().point;
|
||||
std::size_t offset{0};
|
||||
for (const Point &point : xy_path.subspan(1)) {
|
||||
Vec2d unscaled_point{unscaled(point)};
|
||||
Vec2d unscaled_previous_point{unscaled(previous_point)};
|
||||
const double current_segment_length = (unscaled_point - unscaled_previous_point).norm();
|
||||
for (const double distance_to_add : sorted_distances.subspan(offset)) {
|
||||
if (distance_to_add <= total_distance + current_segment_length) {
|
||||
Point to_place = scaled(place_at_segment(
|
||||
unscaled_point, unscaled_previous_point, distance_to_add - total_distance
|
||||
));
|
||||
if (to_place != previous_point && to_place != point) {
|
||||
result.emplace_back(DistancedPoint{to_place, distance_to_add});
|
||||
}
|
||||
++offset;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
total_distance += current_segment_length;
|
||||
result.emplace_back(DistancedPoint{point, total_distance});
|
||||
previous_point = point;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
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
|
||||
240
src/libslic3r/GCode/Travels.hpp
Normal file
240
src/libslic3r/GCode/Travels.hpp
Normal 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> ¤t_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 ¶ms);
|
||||
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_
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user