Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/supermerill/SuperSlicer.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbubnikv <bubnikv@gmail.com>2018-09-19 12:02:24 +0300
committerbubnikv <bubnikv@gmail.com>2018-09-19 12:02:24 +0300
commit0558b53493a77bae44831cf87bb0f59359828ef5 (patch)
treec3e8dbdf7d91a051c12d9ebbf7606d41047fea96 /src/libslic3r/GCode
parent3ddaccb6410478ad02d8c0e02d6d8e6eb1785b9f (diff)
WIP: Moved sources int src/, separated most of the source code from Perl.mass_rename
The XS was left only for the unit / integration tests, and it links libslic3r only. No wxWidgets are allowed to be used from Perl starting from now.
Diffstat (limited to 'src/libslic3r/GCode')
-rw-r--r--src/libslic3r/GCode/Analyzer.cpp842
-rw-r--r--src/libslic3r/GCode/Analyzer.hpp233
-rw-r--r--src/libslic3r/GCode/CoolingBuffer.cpp749
-rw-r--r--src/libslic3r/GCode/CoolingBuffer.hpp53
-rw-r--r--src/libslic3r/GCode/PostProcessor.cpp60
-rw-r--r--src/libslic3r/GCode/PostProcessor.hpp15
-rw-r--r--src/libslic3r/GCode/PressureEqualizer.cpp621
-rw-r--r--src/libslic3r/GCode/PressureEqualizer.hpp212
-rw-r--r--src/libslic3r/GCode/PreviewData.cpp456
-rw-r--r--src/libslic3r/GCode/PreviewData.hpp208
-rw-r--r--src/libslic3r/GCode/PrintExtents.cpp186
-rw-r--r--src/libslic3r/GCode/PrintExtents.hpp30
-rw-r--r--src/libslic3r/GCode/SpiralVase.cpp87
-rw-r--r--src/libslic3r/GCode/SpiralVase.hpp28
-rw-r--r--src/libslic3r/GCode/ToolOrdering.cpp631
-rw-r--r--src/libslic3r/GCode/ToolOrdering.hpp162
-rw-r--r--src/libslic3r/GCode/WipeTower.hpp167
-rw-r--r--src/libslic3r/GCode/WipeTowerPrusaMM.cpp1258
-rw-r--r--src/libslic3r/GCode/WipeTowerPrusaMM.hpp374
19 files changed, 6372 insertions, 0 deletions
diff --git a/src/libslic3r/GCode/Analyzer.cpp b/src/libslic3r/GCode/Analyzer.cpp
new file mode 100644
index 000000000..51d5b1a06
--- /dev/null
+++ b/src/libslic3r/GCode/Analyzer.cpp
@@ -0,0 +1,842 @@
+#include <memory.h>
+#include <string.h>
+#include <float.h>
+
+#include "../libslic3r.h"
+#include "../PrintConfig.hpp"
+#include "Print.hpp"
+
+#include "Analyzer.hpp"
+#include "PreviewData.hpp"
+
+static const std::string AXIS_STR = "XYZE";
+static const float MMMIN_TO_MMSEC = 1.0f / 60.0f;
+static const float INCHES_TO_MM = 25.4f;
+static const float DEFAULT_FEEDRATE = 0.0f;
+static const unsigned int DEFAULT_EXTRUDER_ID = 0;
+static const Slic3r::Vec3d DEFAULT_START_POSITION = Slic3r::Vec3d(0.0f, 0.0f, 0.0f);
+static const float DEFAULT_START_EXTRUSION = 0.0f;
+
+namespace Slic3r {
+
+const std::string GCodeAnalyzer::Extrusion_Role_Tag = "_ANALYZER_EXTR_ROLE:";
+const std::string GCodeAnalyzer::Mm3_Per_Mm_Tag = "_ANALYZER_MM3_PER_MM:";
+const std::string GCodeAnalyzer::Width_Tag = "_ANALYZER_WIDTH:";
+const std::string GCodeAnalyzer::Height_Tag = "_ANALYZER_HEIGHT:";
+
+const double GCodeAnalyzer::Default_mm3_per_mm = 0.0;
+const float GCodeAnalyzer::Default_Width = 0.0f;
+const float GCodeAnalyzer::Default_Height = 0.0f;
+
+GCodeAnalyzer::Metadata::Metadata()
+ : extrusion_role(erNone)
+ , extruder_id(DEFAULT_EXTRUDER_ID)
+ , mm3_per_mm(GCodeAnalyzer::Default_mm3_per_mm)
+ , width(GCodeAnalyzer::Default_Width)
+ , height(GCodeAnalyzer::Default_Height)
+ , feedrate(DEFAULT_FEEDRATE)
+{
+}
+
+GCodeAnalyzer::Metadata::Metadata(ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate)
+ : extrusion_role(extrusion_role)
+ , extruder_id(extruder_id)
+ , mm3_per_mm(mm3_per_mm)
+ , width(width)
+ , height(height)
+ , feedrate(feedrate)
+{
+}
+
+bool GCodeAnalyzer::Metadata::operator != (const GCodeAnalyzer::Metadata& other) const
+{
+ if (extrusion_role != other.extrusion_role)
+ return true;
+
+ if (extruder_id != other.extruder_id)
+ return true;
+
+ if (mm3_per_mm != other.mm3_per_mm)
+ return true;
+
+ if (width != other.width)
+ return true;
+
+ if (height != other.height)
+ return true;
+
+ if (feedrate != other.feedrate)
+ return true;
+
+ return false;
+}
+
+GCodeAnalyzer::GCodeMove::GCodeMove(GCodeMove::EType type, ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder)
+ : type(type)
+ , data(extrusion_role, extruder_id, mm3_per_mm, width, height, feedrate)
+ , start_position(start_position)
+ , end_position(end_position)
+ , delta_extruder(delta_extruder)
+{
+}
+
+GCodeAnalyzer::GCodeMove::GCodeMove(GCodeMove::EType type, const GCodeAnalyzer::Metadata& data, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder)
+ : type(type)
+ , data(data)
+ , start_position(start_position)
+ , end_position(end_position)
+ , delta_extruder(delta_extruder)
+{
+}
+
+GCodeAnalyzer::GCodeAnalyzer()
+{
+ reset();
+}
+
+void GCodeAnalyzer::reset()
+{
+ _set_units(Millimeters);
+ _set_global_positioning_type(Absolute);
+ _set_e_local_positioning_type(Absolute);
+ _set_extrusion_role(erNone);
+ _set_extruder_id(DEFAULT_EXTRUDER_ID);
+ _set_mm3_per_mm(Default_mm3_per_mm);
+ _set_width(Default_Width);
+ _set_height(Default_Height);
+ _set_feedrate(DEFAULT_FEEDRATE);
+ _set_start_position(DEFAULT_START_POSITION);
+ _set_start_extrusion(DEFAULT_START_EXTRUSION);
+ _reset_axes_position();
+
+ m_moves_map.clear();
+}
+
+const std::string& GCodeAnalyzer::process_gcode(const std::string& gcode)
+{
+ m_process_output = "";
+
+ m_parser.parse_buffer(gcode,
+ [this](GCodeReader& reader, const GCodeReader::GCodeLine& line)
+ { this->_process_gcode_line(reader, line); });
+
+ return m_process_output;
+}
+
+void GCodeAnalyzer::calc_gcode_preview_data(GCodePreviewData& preview_data)
+{
+ // resets preview data
+ preview_data.reset();
+
+ // calculates extrusion layers
+ _calc_gcode_preview_extrusion_layers(preview_data);
+
+ // calculates travel
+ _calc_gcode_preview_travel(preview_data);
+
+ // calculates retractions
+ _calc_gcode_preview_retractions(preview_data);
+
+ // calculates unretractions
+ _calc_gcode_preview_unretractions(preview_data);
+}
+
+bool GCodeAnalyzer::is_valid_extrusion_role(ExtrusionRole role)
+{
+ return ((erPerimeter <= role) && (role < erMixed));
+}
+
+void GCodeAnalyzer::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLine& line)
+{
+ // processes 'special' comments contained in line
+ if (_process_tags(line))
+ {
+#if 0
+ // DEBUG ONLY: puts the line back into the gcode
+ m_process_output += line.raw() + "\n";
+#endif
+ return;
+ }
+
+ // sets new start position/extrusion
+ _set_start_position(_get_end_position());
+ _set_start_extrusion(_get_axis_position(E));
+
+ // processes 'normal' gcode lines
+ std::string cmd = line.cmd();
+ if (cmd.length() > 1)
+ {
+ switch (::toupper(cmd[0]))
+ {
+ case 'G':
+ {
+ switch (::atoi(&cmd[1]))
+ {
+ case 1: // Move
+ {
+ _processG1(line);
+ break;
+ }
+ case 10: // Retract
+ {
+ _processG10(line);
+ break;
+ }
+ case 11: // Unretract
+ {
+ _processG11(line);
+ break;
+ }
+ case 22: // Firmware controlled Retract
+ {
+ _processG22(line);
+ break;
+ }
+ case 23: // Firmware controlled Unretract
+ {
+ _processG23(line);
+ break;
+ }
+ case 90: // Set to Absolute Positioning
+ {
+ _processG90(line);
+ break;
+ }
+ case 91: // Set to Relative Positioning
+ {
+ _processG91(line);
+ break;
+ }
+ case 92: // Set Position
+ {
+ _processG92(line);
+ break;
+ }
+ }
+
+ break;
+ }
+ case 'M':
+ {
+ switch (::atoi(&cmd[1]))
+ {
+ case 82: // Set extruder to absolute mode
+ {
+ _processM82(line);
+ break;
+ }
+ case 83: // Set extruder to relative mode
+ {
+ _processM83(line);
+ break;
+ }
+ }
+
+ break;
+ }
+ case 'T': // Select Tools
+ {
+ _processT(line);
+ break;
+ }
+ }
+ }
+
+ // puts the line back into the gcode
+ m_process_output += line.raw() + "\n";
+}
+
+// Returns the new absolute position on the given axis in dependence of the given parameters
+float axis_absolute_position_from_G1_line(GCodeAnalyzer::EAxis axis, const GCodeReader::GCodeLine& lineG1, GCodeAnalyzer::EUnits units, bool is_relative, float current_absolute_position)
+{
+ float lengthsScaleFactor = (units == GCodeAnalyzer::Inches) ? INCHES_TO_MM : 1.0f;
+ if (lineG1.has(Slic3r::Axis(axis)))
+ {
+ float ret = lineG1.value(Slic3r::Axis(axis)) * lengthsScaleFactor;
+ return is_relative ? current_absolute_position + ret : ret;
+ }
+ else
+ return current_absolute_position;
+}
+
+void GCodeAnalyzer::_processG1(const GCodeReader::GCodeLine& line)
+{
+ // updates axes positions from line
+ EUnits units = _get_units();
+ float new_pos[Num_Axis];
+ for (unsigned char a = X; a < Num_Axis; ++a)
+ {
+ bool is_relative = (_get_global_positioning_type() == Relative);
+ if (a == E)
+ is_relative |= (_get_e_local_positioning_type() == Relative);
+
+ new_pos[a] = axis_absolute_position_from_G1_line((EAxis)a, line, units, is_relative, _get_axis_position((EAxis)a));
+ }
+
+ // updates feedrate from line, if present
+ if (line.has_f())
+ _set_feedrate(line.f() * MMMIN_TO_MMSEC);
+
+ // calculates movement deltas
+ float delta_pos[Num_Axis];
+ for (unsigned char a = X; a < Num_Axis; ++a)
+ {
+ delta_pos[a] = new_pos[a] - _get_axis_position((EAxis)a);
+ }
+
+ // Detects move type
+ GCodeMove::EType type = GCodeMove::Noop;
+
+ if (delta_pos[E] < 0.0f)
+ {
+ if ((delta_pos[X] != 0.0f) || (delta_pos[Y] != 0.0f) || (delta_pos[Z] != 0.0f))
+ type = GCodeMove::Move;
+ else
+ type = GCodeMove::Retract;
+ }
+ else if (delta_pos[E] > 0.0f)
+ {
+ if ((delta_pos[X] == 0.0f) && (delta_pos[Y] == 0.0f) && (delta_pos[Z] == 0.0f))
+ type = GCodeMove::Unretract;
+ else if ((delta_pos[X] != 0.0f) || (delta_pos[Y] != 0.0f))
+ type = GCodeMove::Extrude;
+ }
+ else if ((delta_pos[X] != 0.0f) || (delta_pos[Y] != 0.0f) || (delta_pos[Z] != 0.0f))
+ type = GCodeMove::Move;
+
+ ExtrusionRole role = _get_extrusion_role();
+ if ((type == GCodeMove::Extrude) && ((_get_width() == 0.0f) || (_get_height() == 0.0f) || !is_valid_extrusion_role(role)))
+ type = GCodeMove::Move;
+
+ // updates axis positions
+ for (unsigned char a = X; a < Num_Axis; ++a)
+ {
+ _set_axis_position((EAxis)a, new_pos[a]);
+ }
+
+ // stores the move
+ if (type != GCodeMove::Noop)
+ _store_move(type);
+}
+
+void GCodeAnalyzer::_processG10(const GCodeReader::GCodeLine& line)
+{
+ // stores retract move
+ _store_move(GCodeMove::Retract);
+}
+
+void GCodeAnalyzer::_processG11(const GCodeReader::GCodeLine& line)
+{
+ // stores unretract move
+ _store_move(GCodeMove::Unretract);
+}
+
+void GCodeAnalyzer::_processG22(const GCodeReader::GCodeLine& line)
+{
+ // stores retract move
+ _store_move(GCodeMove::Retract);
+}
+
+void GCodeAnalyzer::_processG23(const GCodeReader::GCodeLine& line)
+{
+ // stores unretract move
+ _store_move(GCodeMove::Unretract);
+}
+
+void GCodeAnalyzer::_processG90(const GCodeReader::GCodeLine& line)
+{
+ _set_global_positioning_type(Absolute);
+}
+
+void GCodeAnalyzer::_processG91(const GCodeReader::GCodeLine& line)
+{
+ _set_global_positioning_type(Relative);
+}
+
+void GCodeAnalyzer::_processG92(const GCodeReader::GCodeLine& line)
+{
+ float lengthsScaleFactor = (_get_units() == Inches) ? INCHES_TO_MM : 1.0f;
+ bool anyFound = false;
+
+ if (line.has_x())
+ {
+ _set_axis_position(X, line.x() * lengthsScaleFactor);
+ anyFound = true;
+ }
+
+ if (line.has_y())
+ {
+ _set_axis_position(Y, line.y() * lengthsScaleFactor);
+ anyFound = true;
+ }
+
+ if (line.has_z())
+ {
+ _set_axis_position(Z, line.z() * lengthsScaleFactor);
+ anyFound = true;
+ }
+
+ if (line.has_e())
+ {
+ _set_axis_position(E, line.e() * lengthsScaleFactor);
+ anyFound = true;
+ }
+
+ if (!anyFound)
+ {
+ for (unsigned char a = X; a < Num_Axis; ++a)
+ {
+ _set_axis_position((EAxis)a, 0.0f);
+ }
+ }
+}
+
+void GCodeAnalyzer::_processM82(const GCodeReader::GCodeLine& line)
+{
+ _set_e_local_positioning_type(Absolute);
+}
+
+void GCodeAnalyzer::_processM83(const GCodeReader::GCodeLine& line)
+{
+ _set_e_local_positioning_type(Relative);
+}
+
+void GCodeAnalyzer::_processT(const GCodeReader::GCodeLine& line)
+{
+ std::string cmd = line.cmd();
+ if (cmd.length() > 1)
+ {
+ unsigned int id = (unsigned int)::strtol(cmd.substr(1).c_str(), nullptr, 10);
+ if (_get_extruder_id() != id)
+ {
+ _set_extruder_id(id);
+
+ // stores tool change move
+ _store_move(GCodeMove::Tool_change);
+ }
+ }
+}
+
+bool GCodeAnalyzer::_process_tags(const GCodeReader::GCodeLine& line)
+{
+ std::string comment = line.comment();
+
+ // extrusion role tag
+ size_t pos = comment.find(Extrusion_Role_Tag);
+ if (pos != comment.npos)
+ {
+ _process_extrusion_role_tag(comment, pos);
+ return true;
+ }
+
+ // mm3 per mm tag
+ pos = comment.find(Mm3_Per_Mm_Tag);
+ if (pos != comment.npos)
+ {
+ _process_mm3_per_mm_tag(comment, pos);
+ return true;
+ }
+
+ // width tag
+ pos = comment.find(Width_Tag);
+ if (pos != comment.npos)
+ {
+ _process_width_tag(comment, pos);
+ return true;
+ }
+
+ // height tag
+ pos = comment.find(Height_Tag);
+ if (pos != comment.npos)
+ {
+ _process_height_tag(comment, pos);
+ return true;
+ }
+
+ return false;
+}
+
+void GCodeAnalyzer::_process_extrusion_role_tag(const std::string& comment, size_t pos)
+{
+ int role = (int)::strtol(comment.substr(pos + Extrusion_Role_Tag.length()).c_str(), nullptr, 10);
+ if (_is_valid_extrusion_role(role))
+ _set_extrusion_role((ExtrusionRole)role);
+ else
+ {
+ // todo: show some error ?
+ }
+}
+
+void GCodeAnalyzer::_process_mm3_per_mm_tag(const std::string& comment, size_t pos)
+{
+ _set_mm3_per_mm(::strtod(comment.substr(pos + Mm3_Per_Mm_Tag.length()).c_str(), nullptr));
+}
+
+void GCodeAnalyzer::_process_width_tag(const std::string& comment, size_t pos)
+{
+ _set_width((float)::strtod(comment.substr(pos + Width_Tag.length()).c_str(), nullptr));
+}
+
+void GCodeAnalyzer::_process_height_tag(const std::string& comment, size_t pos)
+{
+ _set_height((float)::strtod(comment.substr(pos + Height_Tag.length()).c_str(), nullptr));
+}
+
+void GCodeAnalyzer::_set_units(GCodeAnalyzer::EUnits units)
+{
+ m_state.units = units;
+}
+
+GCodeAnalyzer::EUnits GCodeAnalyzer::_get_units() const
+{
+ return m_state.units;
+}
+
+void GCodeAnalyzer::_set_global_positioning_type(GCodeAnalyzer::EPositioningType type)
+{
+ m_state.global_positioning_type = type;
+}
+
+GCodeAnalyzer::EPositioningType GCodeAnalyzer::_get_global_positioning_type() const
+{
+ return m_state.global_positioning_type;
+}
+
+void GCodeAnalyzer::_set_e_local_positioning_type(GCodeAnalyzer::EPositioningType type)
+{
+ m_state.e_local_positioning_type = type;
+}
+
+GCodeAnalyzer::EPositioningType GCodeAnalyzer::_get_e_local_positioning_type() const
+{
+ return m_state.e_local_positioning_type;
+}
+
+void GCodeAnalyzer::_set_extrusion_role(ExtrusionRole extrusion_role)
+{
+ m_state.data.extrusion_role = extrusion_role;
+}
+
+ExtrusionRole GCodeAnalyzer::_get_extrusion_role() const
+{
+ return m_state.data.extrusion_role;
+}
+
+void GCodeAnalyzer::_set_extruder_id(unsigned int id)
+{
+ m_state.data.extruder_id = id;
+}
+
+unsigned int GCodeAnalyzer::_get_extruder_id() const
+{
+ return m_state.data.extruder_id;
+}
+
+void GCodeAnalyzer::_set_mm3_per_mm(double value)
+{
+ m_state.data.mm3_per_mm = value;
+}
+
+double GCodeAnalyzer::_get_mm3_per_mm() const
+{
+ return m_state.data.mm3_per_mm;
+}
+
+void GCodeAnalyzer::_set_width(float width)
+{
+ m_state.data.width = width;
+}
+
+float GCodeAnalyzer::_get_width() const
+{
+ return m_state.data.width;
+}
+
+void GCodeAnalyzer::_set_height(float height)
+{
+ m_state.data.height = height;
+}
+
+float GCodeAnalyzer::_get_height() const
+{
+ return m_state.data.height;
+}
+
+void GCodeAnalyzer::_set_feedrate(float feedrate_mm_sec)
+{
+ m_state.data.feedrate = feedrate_mm_sec;
+}
+
+float GCodeAnalyzer::_get_feedrate() const
+{
+ return m_state.data.feedrate;
+}
+
+void GCodeAnalyzer::_set_axis_position(EAxis axis, float position)
+{
+ m_state.position[axis] = position;
+}
+
+float GCodeAnalyzer::_get_axis_position(EAxis axis) const
+{
+ return m_state.position[axis];
+}
+
+void GCodeAnalyzer::_reset_axes_position()
+{
+ ::memset((void*)m_state.position, 0, Num_Axis * sizeof(float));
+}
+
+void GCodeAnalyzer::_set_start_position(const Vec3d& position)
+{
+ m_state.start_position = position;
+}
+
+const Vec3d& GCodeAnalyzer::_get_start_position() const
+{
+ return m_state.start_position;
+}
+
+void GCodeAnalyzer::_set_start_extrusion(float extrusion)
+{
+ m_state.start_extrusion = extrusion;
+}
+
+float GCodeAnalyzer::_get_start_extrusion() const
+{
+ return m_state.start_extrusion;
+}
+
+float GCodeAnalyzer::_get_delta_extrusion() const
+{
+ return _get_axis_position(E) - m_state.start_extrusion;
+}
+
+Vec3d GCodeAnalyzer::_get_end_position() const
+{
+ return Vec3d(m_state.position[X], m_state.position[Y], m_state.position[Z]);
+}
+
+void GCodeAnalyzer::_store_move(GCodeAnalyzer::GCodeMove::EType type)
+{
+ // if type non mapped yet, map it
+ TypeToMovesMap::iterator it = m_moves_map.find(type);
+ if (it == m_moves_map.end())
+ it = m_moves_map.insert(TypeToMovesMap::value_type(type, GCodeMovesList())).first;
+
+ // store move
+ it->second.emplace_back(type, _get_extrusion_role(), _get_extruder_id(), _get_mm3_per_mm(), _get_width(), _get_height(), _get_feedrate(), _get_start_position(), _get_end_position(), _get_delta_extrusion());
+}
+
+bool GCodeAnalyzer::_is_valid_extrusion_role(int value) const
+{
+ return ((int)erNone <= value) && (value <= (int)erMixed);
+}
+
+void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& preview_data)
+{
+ struct Helper
+ {
+ static GCodePreviewData::Extrusion::Layer& get_layer_at_z(GCodePreviewData::Extrusion::LayersList& layers, float z)
+ {
+ for (GCodePreviewData::Extrusion::Layer& layer : layers)
+ {
+ // if layer found, return it
+ if (layer.z == z)
+ return layer;
+ }
+
+ // if layer not found, create and return it
+ layers.emplace_back(z, ExtrusionPaths());
+ return layers.back();
+ }
+
+ static void store_polyline(const Polyline& polyline, const Metadata& data, float z, GCodePreviewData& preview_data)
+ {
+ // if the polyline is valid, create the extrusion path from it and store it
+ if (polyline.is_valid())
+ {
+ ExtrusionPath path(data.extrusion_role, data.mm3_per_mm, data.width, data.height);
+ path.polyline = polyline;
+ path.feedrate = data.feedrate;
+ path.extruder_id = data.extruder_id;
+
+ get_layer_at_z(preview_data.extrusion.layers, z).paths.push_back(path);
+ }
+ }
+ };
+
+ TypeToMovesMap::iterator extrude_moves = m_moves_map.find(GCodeMove::Extrude);
+ if (extrude_moves == m_moves_map.end())
+ return;
+
+ Metadata data;
+ float z = FLT_MAX;
+ Polyline polyline;
+ Vec3d position(FLT_MAX, FLT_MAX, FLT_MAX);
+ float volumetric_rate = FLT_MAX;
+ GCodePreviewData::Range height_range;
+ GCodePreviewData::Range width_range;
+ GCodePreviewData::Range feedrate_range;
+ GCodePreviewData::Range volumetric_rate_range;
+
+ // constructs the polylines while traversing the moves
+ for (const GCodeMove& move : extrude_moves->second)
+ {
+ if ((data != move.data) || (z != move.start_position.z()) || (position != move.start_position) || (volumetric_rate != move.data.feedrate * (float)move.data.mm3_per_mm))
+ {
+ // store current polyline
+ polyline.remove_duplicate_points();
+ Helper::store_polyline(polyline, data, z, preview_data);
+
+ // reset current polyline
+ polyline = Polyline();
+
+ // add both vertices of the move
+ polyline.append(Point(scale_(move.start_position.x()), scale_(move.start_position.y())));
+ polyline.append(Point(scale_(move.end_position.x()), scale_(move.end_position.y())));
+
+ // update current values
+ data = move.data;
+ z = move.start_position.z();
+ volumetric_rate = move.data.feedrate * (float)move.data.mm3_per_mm;
+ height_range.update_from(move.data.height);
+ width_range.update_from(move.data.width);
+ feedrate_range.update_from(move.data.feedrate);
+ volumetric_rate_range.update_from(volumetric_rate);
+ }
+ else
+ // append end vertex of the move to current polyline
+ polyline.append(Point(scale_(move.end_position.x()), scale_(move.end_position.y())));
+
+ // update current values
+ position = move.end_position;
+ }
+
+ // store last polyline
+ polyline.remove_duplicate_points();
+ Helper::store_polyline(polyline, data, z, preview_data);
+
+ // updates preview ranges data
+ preview_data.ranges.height.update_from(height_range);
+ preview_data.ranges.width.update_from(width_range);
+ preview_data.ranges.feedrate.update_from(feedrate_range);
+ preview_data.ranges.volumetric_rate.update_from(volumetric_rate_range);
+}
+
+void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data)
+{
+ struct Helper
+ {
+ static void store_polyline(const Polyline3& polyline, GCodePreviewData::Travel::EType type, GCodePreviewData::Travel::Polyline::EDirection direction,
+ float feedrate, unsigned int extruder_id, GCodePreviewData& preview_data)
+ {
+ // if the polyline is valid, store it
+ if (polyline.is_valid())
+ preview_data.travel.polylines.emplace_back(type, direction, feedrate, extruder_id, polyline);
+ }
+ };
+
+ TypeToMovesMap::iterator travel_moves = m_moves_map.find(GCodeMove::Move);
+ if (travel_moves == m_moves_map.end())
+ return;
+
+ Polyline3 polyline;
+ Vec3d position(FLT_MAX, FLT_MAX, FLT_MAX);
+ GCodePreviewData::Travel::EType type = GCodePreviewData::Travel::Num_Types;
+ GCodePreviewData::Travel::Polyline::EDirection direction = GCodePreviewData::Travel::Polyline::Num_Directions;
+ float feedrate = FLT_MAX;
+ unsigned int extruder_id = -1;
+
+ GCodePreviewData::Range height_range;
+ GCodePreviewData::Range width_range;
+ GCodePreviewData::Range feedrate_range;
+
+ // constructs the polylines while traversing the moves
+ for (const GCodeMove& move : travel_moves->second)
+ {
+ GCodePreviewData::Travel::EType move_type = (move.delta_extruder < 0.0f) ? GCodePreviewData::Travel::Retract : ((move.delta_extruder > 0.0f) ? GCodePreviewData::Travel::Extrude : GCodePreviewData::Travel::Move);
+ GCodePreviewData::Travel::Polyline::EDirection move_direction = ((move.start_position.x() != move.end_position.x()) || (move.start_position.y() != move.end_position.y())) ? GCodePreviewData::Travel::Polyline::Generic : GCodePreviewData::Travel::Polyline::Vertical;
+
+ if ((type != move_type) || (direction != move_direction) || (feedrate != move.data.feedrate) || (position != move.start_position) || (extruder_id != move.data.extruder_id))
+ {
+ // store current polyline
+ polyline.remove_duplicate_points();
+ Helper::store_polyline(polyline, type, direction, feedrate, extruder_id, preview_data);
+
+ // reset current polyline
+ polyline = Polyline3();
+
+ // add both vertices of the move
+ polyline.append(Vec3crd(scale_(move.start_position.x()), scale_(move.start_position.y()), scale_(move.start_position.z())));
+ polyline.append(Vec3crd(scale_(move.end_position.x()), scale_(move.end_position.y()), scale_(move.end_position.z())));
+ }
+ else
+ // append end vertex of the move to current polyline
+ polyline.append(Vec3crd(scale_(move.end_position.x()), scale_(move.end_position.y()), scale_(move.end_position.z())));
+
+ // update current values
+ position = move.end_position;
+ type = move_type;
+ feedrate = move.data.feedrate;
+ extruder_id = move.data.extruder_id;
+ height_range.update_from(move.data.height);
+ width_range.update_from(move.data.width);
+ feedrate_range.update_from(move.data.feedrate);
+ }
+
+ // store last polyline
+ polyline.remove_duplicate_points();
+ Helper::store_polyline(polyline, type, direction, feedrate, extruder_id, preview_data);
+
+ // updates preview ranges data
+ preview_data.ranges.height.update_from(height_range);
+ preview_data.ranges.width.update_from(width_range);
+ preview_data.ranges.feedrate.update_from(feedrate_range);
+}
+
+void GCodeAnalyzer::_calc_gcode_preview_retractions(GCodePreviewData& preview_data)
+{
+ TypeToMovesMap::iterator retraction_moves = m_moves_map.find(GCodeMove::Retract);
+ if (retraction_moves == m_moves_map.end())
+ return;
+
+ for (const GCodeMove& move : retraction_moves->second)
+ {
+ // store position
+ Vec3crd position(scale_(move.start_position.x()), scale_(move.start_position.y()), scale_(move.start_position.z()));
+ preview_data.retraction.positions.emplace_back(position, move.data.width, move.data.height);
+ }
+}
+
+void GCodeAnalyzer::_calc_gcode_preview_unretractions(GCodePreviewData& preview_data)
+{
+ TypeToMovesMap::iterator unretraction_moves = m_moves_map.find(GCodeMove::Unretract);
+ if (unretraction_moves == m_moves_map.end())
+ return;
+
+ for (const GCodeMove& move : unretraction_moves->second)
+ {
+ // store position
+ Vec3crd position(scale_(move.start_position.x()), scale_(move.start_position.y()), scale_(move.start_position.z()));
+ preview_data.unretraction.positions.emplace_back(position, move.data.width, move.data.height);
+ }
+}
+
+GCodePreviewData::Color operator + (const GCodePreviewData::Color& c1, const GCodePreviewData::Color& c2)
+{
+ return GCodePreviewData::Color(clamp(0.0f, 1.0f, c1.rgba[0] + c2.rgba[0]),
+ clamp(0.0f, 1.0f, c1.rgba[1] + c2.rgba[1]),
+ clamp(0.0f, 1.0f, c1.rgba[2] + c2.rgba[2]),
+ clamp(0.0f, 1.0f, c1.rgba[3] + c2.rgba[3]));
+}
+
+GCodePreviewData::Color operator * (float f, const GCodePreviewData::Color& color)
+{
+ return GCodePreviewData::Color(clamp(0.0f, 1.0f, f * color.rgba[0]),
+ clamp(0.0f, 1.0f, f * color.rgba[1]),
+ clamp(0.0f, 1.0f, f * color.rgba[2]),
+ clamp(0.0f, 1.0f, f * color.rgba[3]));
+}
+
+} // namespace Slic3r
diff --git a/src/libslic3r/GCode/Analyzer.hpp b/src/libslic3r/GCode/Analyzer.hpp
new file mode 100644
index 000000000..27a49b869
--- /dev/null
+++ b/src/libslic3r/GCode/Analyzer.hpp
@@ -0,0 +1,233 @@
+#ifndef slic3r_GCode_Analyzer_hpp_
+#define slic3r_GCode_Analyzer_hpp_
+
+#include "../libslic3r.h"
+#include "../PrintConfig.hpp"
+#include "../ExtrusionEntity.hpp"
+
+#include "Point.hpp"
+#include "GCodeReader.hpp"
+
+namespace Slic3r {
+
+class GCodePreviewData;
+
+class GCodeAnalyzer
+{
+public:
+ static const std::string Extrusion_Role_Tag;
+ static const std::string Mm3_Per_Mm_Tag;
+ static const std::string Width_Tag;
+ static const std::string Height_Tag;
+
+ static const double Default_mm3_per_mm;
+ static const float Default_Width;
+ static const float Default_Height;
+
+ enum EUnits : unsigned char
+ {
+ Millimeters,
+ Inches
+ };
+
+ enum EAxis : unsigned char
+ {
+ X,
+ Y,
+ Z,
+ E,
+ Num_Axis
+ };
+
+ enum EPositioningType : unsigned char
+ {
+ Absolute,
+ Relative
+ };
+
+ struct Metadata
+ {
+ ExtrusionRole extrusion_role;
+ unsigned int extruder_id;
+ double mm3_per_mm;
+ float width; // mm
+ float height; // mm
+ float feedrate; // mm/s
+
+ Metadata();
+ Metadata(ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate);
+
+ bool operator != (const Metadata& other) const;
+ };
+
+ struct GCodeMove
+ {
+ enum EType : unsigned char
+ {
+ Noop,
+ Retract,
+ Unretract,
+ Tool_change,
+ Move,
+ Extrude,
+ Num_Types
+ };
+
+ EType type;
+ Metadata data;
+ Vec3d start_position;
+ Vec3d end_position;
+ float delta_extruder;
+
+ GCodeMove(EType type, ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder);
+ GCodeMove(EType type, const Metadata& data, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder);
+ };
+
+ typedef std::vector<GCodeMove> GCodeMovesList;
+ typedef std::map<GCodeMove::EType, GCodeMovesList> TypeToMovesMap;
+
+private:
+ struct State
+ {
+ EUnits units;
+ EPositioningType global_positioning_type;
+ EPositioningType e_local_positioning_type;
+ Metadata data;
+ Vec3d start_position = Vec3d::Zero();
+ float start_extrusion;
+ float position[Num_Axis];
+ };
+
+private:
+ State m_state;
+ GCodeReader m_parser;
+ TypeToMovesMap m_moves_map;
+
+ // The output of process_layer()
+ std::string m_process_output;
+
+public:
+ GCodeAnalyzer();
+
+ // Reinitialize the analyzer
+ void reset();
+
+ // Adds the gcode contained in the given string to the analysis and returns it after removing the workcodes
+ const std::string& process_gcode(const std::string& gcode);
+
+ // Calculates all data needed for gcode visualization
+ void calc_gcode_preview_data(GCodePreviewData& preview_data);
+
+ static bool is_valid_extrusion_role(ExtrusionRole role);
+
+private:
+ // Processes the given gcode line
+ void _process_gcode_line(GCodeReader& reader, const GCodeReader::GCodeLine& line);
+
+ // Move
+ void _processG1(const GCodeReader::GCodeLine& line);
+
+ // Retract
+ void _processG10(const GCodeReader::GCodeLine& line);
+
+ // Unretract
+ void _processG11(const GCodeReader::GCodeLine& line);
+
+ // Firmware controlled Retract
+ void _processG22(const GCodeReader::GCodeLine& line);
+
+ // Firmware controlled Unretract
+ void _processG23(const GCodeReader::GCodeLine& line);
+
+ // Set to Absolute Positioning
+ void _processG90(const GCodeReader::GCodeLine& line);
+
+ // Set to Relative Positioning
+ void _processG91(const GCodeReader::GCodeLine& line);
+
+ // Set Position
+ void _processG92(const GCodeReader::GCodeLine& line);
+
+ // Set extruder to absolute mode
+ void _processM82(const GCodeReader::GCodeLine& line);
+
+ // Set extruder to relative mode
+ void _processM83(const GCodeReader::GCodeLine& line);
+
+ // Processes T line (Select Tool)
+ void _processT(const GCodeReader::GCodeLine& line);
+
+ // Processes the tags
+ // Returns true if any tag has been processed
+ bool _process_tags(const GCodeReader::GCodeLine& line);
+
+ // Processes extrusion role tag
+ void _process_extrusion_role_tag(const std::string& comment, size_t pos);
+
+ // Processes mm3_per_mm tag
+ void _process_mm3_per_mm_tag(const std::string& comment, size_t pos);
+
+ // Processes width tag
+ void _process_width_tag(const std::string& comment, size_t pos);
+
+ // Processes height tag
+ void _process_height_tag(const std::string& comment, size_t pos);
+
+ void _set_units(EUnits units);
+ EUnits _get_units() const;
+
+ void _set_global_positioning_type(EPositioningType type);
+ EPositioningType _get_global_positioning_type() const;
+
+ void _set_e_local_positioning_type(EPositioningType type);
+ EPositioningType _get_e_local_positioning_type() const;
+
+ void _set_extrusion_role(ExtrusionRole extrusion_role);
+ ExtrusionRole _get_extrusion_role() const;
+
+ void _set_extruder_id(unsigned int id);
+ unsigned int _get_extruder_id() const;
+
+ void _set_mm3_per_mm(double value);
+ double _get_mm3_per_mm() const;
+
+ void _set_width(float width);
+ float _get_width() const;
+
+ void _set_height(float height);
+ float _get_height() const;
+
+ void _set_feedrate(float feedrate_mm_sec);
+ float _get_feedrate() const;
+
+ void _set_axis_position(EAxis axis, float position);
+ float _get_axis_position(EAxis axis) const;
+
+ // Sets axes position to zero
+ void _reset_axes_position();
+
+ void _set_start_position(const Vec3d& position);
+ const Vec3d& _get_start_position() const;
+
+ void _set_start_extrusion(float extrusion);
+ float _get_start_extrusion() const;
+ float _get_delta_extrusion() const;
+
+ // Returns current xyz position (from m_state.position[])
+ Vec3d _get_end_position() const;
+
+ // Adds a new move with the given data
+ void _store_move(GCodeMove::EType type);
+
+ // Checks if the given int is a valid extrusion role (contained into enum ExtrusionRole)
+ bool _is_valid_extrusion_role(int value) const;
+
+ void _calc_gcode_preview_extrusion_layers(GCodePreviewData& preview_data);
+ void _calc_gcode_preview_travel(GCodePreviewData& preview_data);
+ void _calc_gcode_preview_retractions(GCodePreviewData& preview_data);
+ void _calc_gcode_preview_unretractions(GCodePreviewData& preview_data);
+};
+
+} // namespace Slic3r
+
+#endif /* slic3r_GCode_Analyzer_hpp_ */
diff --git a/src/libslic3r/GCode/CoolingBuffer.cpp b/src/libslic3r/GCode/CoolingBuffer.cpp
new file mode 100644
index 000000000..40ccc7b09
--- /dev/null
+++ b/src/libslic3r/GCode/CoolingBuffer.cpp
@@ -0,0 +1,749 @@
+#include "../GCode.hpp"
+#include "CoolingBuffer.hpp"
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#include <iostream>
+#include <float.h>
+
+#if 0
+ #define DEBUG
+ #define _DEBUG
+ #undef NDEBUG
+#endif
+
+#include <assert.h>
+
+namespace Slic3r {
+
+CoolingBuffer::CoolingBuffer(GCode &gcodegen) : m_gcodegen(gcodegen), m_current_extruder(0)
+{
+ this->reset();
+}
+
+void CoolingBuffer::reset()
+{
+ m_current_pos.assign(5, 0.f);
+ Vec3d pos = m_gcodegen.writer().get_position();
+ m_current_pos[0] = float(pos(0));
+ m_current_pos[1] = float(pos(1));
+ m_current_pos[2] = float(pos(2));
+ m_current_pos[4] = float(m_gcodegen.config().travel_speed.value);
+}
+
+struct CoolingLine
+{
+ enum Type {
+ TYPE_SET_TOOL = 1 << 0,
+ TYPE_EXTRUDE_END = 1 << 1,
+ TYPE_BRIDGE_FAN_START = 1 << 2,
+ TYPE_BRIDGE_FAN_END = 1 << 3,
+ TYPE_G0 = 1 << 4,
+ TYPE_G1 = 1 << 5,
+ TYPE_ADJUSTABLE = 1 << 6,
+ TYPE_EXTERNAL_PERIMETER = 1 << 7,
+ // The line sets a feedrate.
+ TYPE_HAS_F = 1 << 8,
+ TYPE_WIPE = 1 << 9,
+ TYPE_G4 = 1 << 10,
+ TYPE_G92 = 1 << 11,
+ };
+
+ CoolingLine(unsigned int type, size_t line_start, size_t line_end) :
+ type(type), line_start(line_start), line_end(line_end),
+ length(0.f), feedrate(0.f), time(0.f), time_max(0.f), slowdown(false) {}
+
+ bool adjustable(bool slowdown_external_perimeters) const {
+ return (this->type & TYPE_ADJUSTABLE) &&
+ (! (this->type & TYPE_EXTERNAL_PERIMETER) || slowdown_external_perimeters) &&
+ this->time < this->time_max;
+ }
+
+ bool adjustable() const {
+ return (this->type & TYPE_ADJUSTABLE) && this->time < this->time_max;
+ }
+
+ size_t type;
+ // Start of this line at the G-code snippet.
+ size_t line_start;
+ // End of this line at the G-code snippet.
+ size_t line_end;
+ // XY Euclidian length of this segment.
+ float length;
+ // Current feedrate, possibly adjusted.
+ float feedrate;
+ // Current duration of this segment.
+ float time;
+ // Maximum duration of this segment.
+ float time_max;
+ // If marked with the "slowdown" flag, the line has been slowed down.
+ bool slowdown;
+};
+
+// Calculate the required per extruder time stretches.
+struct PerExtruderAdjustments
+{
+ // Calculate the total elapsed time per this extruder, adjusted for the slowdown.
+ float elapsed_time_total() {
+ float time_total = 0.f;
+ for (const CoolingLine &line : lines)
+ time_total += line.time;
+ return time_total;
+ }
+ // Calculate the total elapsed time when slowing down
+ // to the minimum extrusion feed rate defined for the current material.
+ float maximum_time_after_slowdown(bool slowdown_external_perimeters) {
+ float time_total = 0.f;
+ for (const CoolingLine &line : lines)
+ if (line.adjustable(slowdown_external_perimeters)) {
+ if (line.time_max == FLT_MAX)
+ return FLT_MAX;
+ else
+ time_total += line.time_max;
+ } else
+ time_total += line.time;
+ return time_total;
+ }
+ // Calculate the adjustable part of the total time.
+ float adjustable_time(bool slowdown_external_perimeters) {
+ float time_total = 0.f;
+ for (const CoolingLine &line : lines)
+ if (line.adjustable(slowdown_external_perimeters))
+ time_total += line.time;
+ return time_total;
+ }
+ // Calculate the non-adjustable part of the total time.
+ float non_adjustable_time(bool slowdown_external_perimeters) {
+ float time_total = 0.f;
+ for (const CoolingLine &line : lines)
+ if (! line.adjustable(slowdown_external_perimeters))
+ time_total += line.time;
+ return time_total;
+ }
+ // Slow down the adjustable extrusions to the minimum feedrate allowed for the current extruder material.
+ // Used by both proportional and non-proportional slow down.
+ float slowdown_to_minimum_feedrate(bool slowdown_external_perimeters) {
+ float time_total = 0.f;
+ for (CoolingLine &line : lines) {
+ if (line.adjustable(slowdown_external_perimeters)) {
+ assert(line.time_max >= 0.f && line.time_max < FLT_MAX);
+ line.slowdown = true;
+ line.time = line.time_max;
+ line.feedrate = line.length / line.time;
+ }
+ time_total += line.time;
+ }
+ return time_total;
+ }
+ // Slow down each adjustable G-code line proportionally by a factor.
+ // Used by the proportional slow down.
+ float slow_down_proportional(float factor, bool slowdown_external_perimeters) {
+ assert(factor >= 1.f);
+ float time_total = 0.f;
+ for (CoolingLine &line : lines) {
+ if (line.adjustable(slowdown_external_perimeters)) {
+ line.slowdown = true;
+ line.time = std::min(line.time_max, line.time * factor);
+ line.feedrate = line.length / line.time;
+ }
+ time_total += line.time;
+ }
+ return time_total;
+ }
+
+ // Sort the lines, adjustable first, higher feedrate first.
+ // Used by non-proportional slow down.
+ void sort_lines_by_decreasing_feedrate() {
+ std::sort(lines.begin(), lines.end(), [](const CoolingLine &l1, const CoolingLine &l2) {
+ bool adj1 = l1.adjustable();
+ bool adj2 = l2.adjustable();
+ return (adj1 == adj2) ? l1.feedrate > l2.feedrate : adj1;
+ });
+ for (n_lines_adjustable = 0;
+ n_lines_adjustable < lines.size() && this->lines[n_lines_adjustable].adjustable();
+ ++ n_lines_adjustable);
+ time_non_adjustable = 0.f;
+ for (size_t i = n_lines_adjustable; i < lines.size(); ++ i)
+ time_non_adjustable += lines[i].time;
+ }
+
+ // Calculate the maximum time stretch when slowing down to min_feedrate.
+ // Slowdown to min_feedrate shall be allowed for this extruder's material.
+ // Used by non-proportional slow down.
+ float time_stretch_when_slowing_down_to_feedrate(float min_feedrate) {
+ float time_stretch = 0.f;
+ assert(this->min_print_speed < min_feedrate + EPSILON);
+ for (size_t i = 0; i < n_lines_adjustable; ++ i) {
+ const CoolingLine &line = lines[i];
+ if (line.feedrate > min_feedrate)
+ time_stretch += line.time * (line.feedrate / min_feedrate - 1.f);
+ }
+ return time_stretch;
+ }
+
+ // Slow down all adjustable lines down to min_feedrate.
+ // Slowdown to min_feedrate shall be allowed for this extruder's material.
+ // Used by non-proportional slow down.
+ void slow_down_to_feedrate(float min_feedrate) {
+ assert(this->min_print_speed < min_feedrate + EPSILON);
+ for (size_t i = 0; i < n_lines_adjustable; ++ i) {
+ CoolingLine &line = lines[i];
+ if (line.feedrate > min_feedrate) {
+ line.time *= std::max(1.f, line.feedrate / min_feedrate);
+ line.feedrate = min_feedrate;
+ line.slowdown = true;
+ }
+ }
+ }
+
+ // Extruder, for which the G-code will be adjusted.
+ unsigned int extruder_id = 0;
+ // Is the cooling slow down logic enabled for this extruder's material?
+ bool cooling_slow_down_enabled = false;
+ // Slow down the print down to min_print_speed if the total layer time is below slowdown_below_layer_time.
+ float slowdown_below_layer_time = 0.f;
+ // Minimum print speed allowed for this extruder.
+ float min_print_speed = 0.f;
+
+ // Parsed lines.
+ std::vector<CoolingLine> lines;
+ // The following two values are set by sort_lines_by_decreasing_feedrate():
+ // Number of adjustable lines, at the start of lines.
+ size_t n_lines_adjustable = 0;
+ // Non-adjustable time of lines starting with n_lines_adjustable.
+ float time_non_adjustable = 0;
+ // Current total time for this extruder.
+ float time_total = 0;
+ // Maximum time for this extruder, when the maximum slow down is applied.
+ float time_maximum = 0;
+
+ // Temporaries for processing the slow down. Both thresholds go from 0 to n_lines_adjustable.
+ size_t idx_line_begin = 0;
+ size_t idx_line_end = 0;
+};
+
+std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_id)
+{
+ std::vector<PerExtruderAdjustments> per_extruder_adjustments = this->parse_layer_gcode(gcode, m_current_pos);
+ float layer_time_stretched = this->calculate_layer_slowdown(per_extruder_adjustments);
+ return this->apply_layer_cooldown(gcode, layer_id, layer_time_stretched, per_extruder_adjustments);
+}
+
+// Parse the layer G-code for the moves, which could be adjusted.
+// Return the list of parsed lines, bucketed by an extruder.
+std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::string &gcode, std::vector<float> &current_pos) const
+{
+ const FullPrintConfig &config = m_gcodegen.config();
+ const std::vector<Extruder> &extruders = m_gcodegen.writer().extruders();
+ unsigned int num_extruders = 0;
+ for (const Extruder &ex : extruders)
+ num_extruders = std::max(ex.id() + 1, num_extruders);
+
+ std::vector<PerExtruderAdjustments> per_extruder_adjustments(extruders.size());
+ std::vector<size_t> map_extruder_to_per_extruder_adjustment(num_extruders, 0);
+ for (size_t i = 0; i < extruders.size(); ++ i) {
+ PerExtruderAdjustments &adj = per_extruder_adjustments[i];
+ unsigned int extruder_id = extruders[i].id();
+ adj.extruder_id = extruder_id;
+ adj.cooling_slow_down_enabled = config.cooling.get_at(extruder_id);
+ adj.slowdown_below_layer_time = config.slowdown_below_layer_time.get_at(extruder_id);
+ adj.min_print_speed = config.min_print_speed.get_at(extruder_id);
+ map_extruder_to_per_extruder_adjustment[extruder_id] = i;
+ }
+
+ const std::string toolchange_prefix = m_gcodegen.writer().toolchange_prefix();
+ unsigned int current_extruder = m_current_extruder;
+ PerExtruderAdjustments *adjustment = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[current_extruder]];
+ const char *line_start = gcode.c_str();
+ const char *line_end = line_start;
+ const char extrusion_axis = config.get_extrusion_axis()[0];
+ // Index of an existing CoolingLine of the current adjustment, which holds the feedrate setting command
+ // for a sequence of extrusion moves.
+ size_t active_speed_modifier = size_t(-1);
+
+ for (; *line_start != 0; line_start = line_end)
+ {
+ while (*line_end != '\n' && *line_end != 0)
+ ++ line_end;
+ // sline will not contain the trailing '\n'.
+ std::string sline(line_start, line_end);
+ // CoolingLine will contain the trailing '\n'.
+ if (*line_end == '\n')
+ ++ line_end;
+ CoolingLine line(0, line_start - gcode.c_str(), line_end - gcode.c_str());
+ if (boost::starts_with(sline, "G0 "))
+ line.type = CoolingLine::TYPE_G0;
+ else if (boost::starts_with(sline, "G1 "))
+ line.type = CoolingLine::TYPE_G1;
+ else if (boost::starts_with(sline, "G92 "))
+ line.type = CoolingLine::TYPE_G92;
+ if (line.type) {
+ // G0, G1 or G92
+ // Parse the G-code line.
+ std::vector<float> new_pos(current_pos);
+ const char *c = sline.data() + 3;
+ for (;;) {
+ // Skip whitespaces.
+ for (; *c == ' ' || *c == '\t'; ++ c);
+ if (*c == 0 || *c == ';')
+ break;
+ // Parse the axis.
+ size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') :
+ (*c == extrusion_axis) ? 3 : (*c == 'F') ? 4 : size_t(-1);
+ if (axis != size_t(-1)) {
+ new_pos[axis] = float(atof(++c));
+ if (axis == 4) {
+ // Convert mm/min to mm/sec.
+ new_pos[4] /= 60.f;
+ if ((line.type & CoolingLine::TYPE_G92) == 0)
+ // This is G0 or G1 line and it sets the feedrate. This mark is used for reducing the duplicate F calls.
+ line.type |= CoolingLine::TYPE_HAS_F;
+ }
+ }
+ // Skip this word.
+ for (; *c != ' ' && *c != '\t' && *c != 0; ++ c);
+ }
+ bool external_perimeter = boost::contains(sline, ";_EXTERNAL_PERIMETER");
+ bool wipe = boost::contains(sline, ";_WIPE");
+ if (external_perimeter)
+ line.type |= CoolingLine::TYPE_EXTERNAL_PERIMETER;
+ if (wipe)
+ line.type |= CoolingLine::TYPE_WIPE;
+ if (boost::contains(sline, ";_EXTRUDE_SET_SPEED") && ! wipe) {
+ line.type |= CoolingLine::TYPE_ADJUSTABLE;
+ active_speed_modifier = adjustment->lines.size();
+ }
+ if ((line.type & CoolingLine::TYPE_G92) == 0) {
+ // G0 or G1. Calculate the duration.
+ if (config.use_relative_e_distances.value)
+ // Reset extruder accumulator.
+ current_pos[3] = 0.f;
+ float dif[4];
+ for (size_t i = 0; i < 4; ++ i)
+ dif[i] = new_pos[i] - current_pos[i];
+ float dxy2 = dif[0] * dif[0] + dif[1] * dif[1];
+ float dxyz2 = dxy2 + dif[2] * dif[2];
+ if (dxyz2 > 0.f) {
+ // Movement in xyz, calculate time from the xyz Euclidian distance.
+ line.length = sqrt(dxyz2);
+ } else if (std::abs(dif[3]) > 0.f) {
+ // Movement in the extruder axis.
+ line.length = std::abs(dif[3]);
+ }
+ line.feedrate = new_pos[4];
+ assert((line.type & CoolingLine::TYPE_ADJUSTABLE) == 0 || line.feedrate > 0.f);
+ if (line.length > 0)
+ line.time = line.length / line.feedrate;
+ line.time_max = line.time;
+ if ((line.type & CoolingLine::TYPE_ADJUSTABLE) || active_speed_modifier != size_t(-1))
+ line.time_max = (adjustment->min_print_speed == 0.f) ? FLT_MAX : std::max(line.time, line.length / adjustment->min_print_speed);
+ if (active_speed_modifier < adjustment->lines.size() && (line.type & CoolingLine::TYPE_G1)) {
+ // Inside the ";_EXTRUDE_SET_SPEED" blocks, there must not be a G1 Fxx entry.
+ assert((line.type & CoolingLine::TYPE_HAS_F) == 0);
+ CoolingLine &sm = adjustment->lines[active_speed_modifier];
+ assert(sm.feedrate > 0.f);
+ sm.length += line.length;
+ sm.time += line.time;
+ if (sm.time_max != FLT_MAX) {
+ if (line.time_max == FLT_MAX)
+ sm.time_max = FLT_MAX;
+ else
+ sm.time_max += line.time_max;
+ }
+ // Don't store this line.
+ line.type = 0;
+ }
+ }
+ current_pos = std::move(new_pos);
+ } else if (boost::starts_with(sline, ";_EXTRUDE_END")) {
+ line.type = CoolingLine::TYPE_EXTRUDE_END;
+ active_speed_modifier = size_t(-1);
+ } else if (boost::starts_with(sline, toolchange_prefix)) {
+ // Switch the tool.
+ line.type = CoolingLine::TYPE_SET_TOOL;
+ unsigned int new_extruder = (unsigned int)atoi(sline.c_str() + toolchange_prefix.size());
+ if (new_extruder != current_extruder) {
+ current_extruder = new_extruder;
+ adjustment = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[current_extruder]];
+ }
+ } else if (boost::starts_with(sline, ";_BRIDGE_FAN_START")) {
+ line.type = CoolingLine::TYPE_BRIDGE_FAN_START;
+ } else if (boost::starts_with(sline, ";_BRIDGE_FAN_END")) {
+ line.type = CoolingLine::TYPE_BRIDGE_FAN_END;
+ } else if (boost::starts_with(sline, "G4 ")) {
+ // Parse the wait time.
+ line.type = CoolingLine::TYPE_G4;
+ size_t pos_S = sline.find('S', 3);
+ size_t pos_P = sline.find('P', 3);
+ line.time = line.time_max = float(
+ (pos_S > 0) ? atof(sline.c_str() + pos_S + 1) :
+ (pos_P > 0) ? atof(sline.c_str() + pos_P + 1) * 0.001 : 0.);
+ }
+ if (line.type != 0)
+ adjustment->lines.emplace_back(std::move(line));
+ }
+
+ return per_extruder_adjustments;
+}
+
+// Slow down an extruder range proportionally down to slowdown_below_layer_time.
+// Return the total time for the complete layer.
+static inline float extruder_range_slow_down_proportional(
+ std::vector<PerExtruderAdjustments*>::iterator it_begin,
+ std::vector<PerExtruderAdjustments*>::iterator it_end,
+ // Elapsed time for the extruders already processed.
+ float elapsed_time_total0,
+ // Initial total elapsed time before slow down.
+ float elapsed_time_before_slowdown,
+ // Target time for the complete layer (all extruders applied).
+ float slowdown_below_layer_time)
+{
+ // Total layer time after the slow down has been applied.
+ float total_after_slowdown = elapsed_time_before_slowdown;
+ // Now decide, whether the external perimeters shall be slowed down as well.
+ float max_time_nep = elapsed_time_total0;
+ for (auto it = it_begin; it != it_end; ++ it)
+ max_time_nep += (*it)->maximum_time_after_slowdown(false);
+ if (max_time_nep > slowdown_below_layer_time) {
+ // It is sufficient to slow down the non-external perimeter moves to reach the target layer time.
+ // Slow down the non-external perimeters proportionally.
+ float non_adjustable_time = elapsed_time_total0;
+ for (auto it = it_begin; it != it_end; ++ it)
+ non_adjustable_time += (*it)->non_adjustable_time(false);
+ // The following step is a linear programming task due to the minimum movement speeds of the print moves.
+ // Run maximum 5 iterations until a good enough approximation is reached.
+ for (size_t iter = 0; iter < 5; ++ iter) {
+ float factor = (slowdown_below_layer_time - non_adjustable_time) / (total_after_slowdown - non_adjustable_time);
+ assert(factor > 1.f);
+ total_after_slowdown = elapsed_time_total0;
+ for (auto it = it_begin; it != it_end; ++ it)
+ total_after_slowdown += (*it)->slow_down_proportional(factor, false);
+ if (total_after_slowdown > 0.95f * slowdown_below_layer_time)
+ break;
+ }
+ } else {
+ // Slow down everything. First slow down the non-external perimeters to maximum.
+ for (auto it = it_begin; it != it_end; ++ it)
+ (*it)->slowdown_to_minimum_feedrate(false);
+ // Slow down the external perimeters proportionally.
+ float non_adjustable_time = elapsed_time_total0;
+ for (auto it = it_begin; it != it_end; ++ it)
+ non_adjustable_time += (*it)->non_adjustable_time(true);
+ for (size_t iter = 0; iter < 5; ++ iter) {
+ float factor = (slowdown_below_layer_time - non_adjustable_time) / (total_after_slowdown - non_adjustable_time);
+ assert(factor > 1.f);
+ total_after_slowdown = elapsed_time_total0;
+ for (auto it = it_begin; it != it_end; ++ it)
+ total_after_slowdown += (*it)->slow_down_proportional(factor, true);
+ if (total_after_slowdown > 0.95f * slowdown_below_layer_time)
+ break;
+ }
+ }
+ return total_after_slowdown;
+}
+
+// Slow down an extruder range to slowdown_below_layer_time.
+// Return the total time for the complete layer.
+static inline void extruder_range_slow_down_non_proportional(
+ std::vector<PerExtruderAdjustments*>::iterator it_begin,
+ std::vector<PerExtruderAdjustments*>::iterator it_end,
+ float time_stretch)
+{
+ // Slow down. Try to equalize the feedrates.
+ std::vector<PerExtruderAdjustments*> by_min_print_speed(it_begin, it_end);
+ // Find the next highest adjustable feedrate among the extruders.
+ float feedrate = 0;
+ for (PerExtruderAdjustments *adj : by_min_print_speed) {
+ adj->idx_line_begin = 0;
+ adj->idx_line_end = 0;
+ assert(adj->idx_line_begin < adj->n_lines_adjustable);
+ if (adj->lines[adj->idx_line_begin].feedrate > feedrate)
+ feedrate = adj->lines[adj->idx_line_begin].feedrate;
+ }
+ assert(feedrate > 0.f);
+ // Sort by min_print_speed, maximum speed first.
+ std::sort(by_min_print_speed.begin(), by_min_print_speed.end(),
+ [](const PerExtruderAdjustments *p1, const PerExtruderAdjustments *p2){ return p1->min_print_speed > p2->min_print_speed; });
+ // Slow down, fast moves first.
+ for (;;) {
+ // For each extruder, find the span of lines with a feedrate close to feedrate.
+ for (PerExtruderAdjustments *adj : by_min_print_speed) {
+ for (adj->idx_line_end = adj->idx_line_begin;
+ adj->idx_line_end < adj->n_lines_adjustable && adj->lines[adj->idx_line_end].feedrate > feedrate - EPSILON;
+ ++ adj->idx_line_end) ;
+ }
+ // Find the next highest adjustable feedrate among the extruders.
+ float feedrate_next = 0.f;
+ for (PerExtruderAdjustments *adj : by_min_print_speed)
+ if (adj->idx_line_end < adj->n_lines_adjustable && adj->lines[adj->idx_line_end].feedrate > feedrate_next)
+ feedrate_next = adj->lines[adj->idx_line_end].feedrate;
+ // Slow down, limited by max(feedrate_next, min_print_speed).
+ for (auto adj = by_min_print_speed.begin(); adj != by_min_print_speed.end();) {
+ // Slow down at most by time_stretch.
+ if ((*adj)->min_print_speed == 0.f) {
+ // All the adjustable speeds are now lowered to the same speed,
+ // and the minimum speed is set to zero.
+ float time_adjustable = 0.f;
+ for (auto it = adj; it != by_min_print_speed.end(); ++ it)
+ time_adjustable += (*it)->adjustable_time(true);
+ float rate = (time_adjustable + time_stretch) / time_adjustable;
+ for (auto it = adj; it != by_min_print_speed.end(); ++ it)
+ (*it)->slow_down_proportional(rate, true);
+ return;
+ } else {
+ float feedrate_limit = std::max(feedrate_next, (*adj)->min_print_speed);
+ bool done = false;
+ float time_stretch_max = 0.f;
+ for (auto it = adj; it != by_min_print_speed.end(); ++ it)
+ time_stretch_max += (*it)->time_stretch_when_slowing_down_to_feedrate(feedrate_limit);
+ if (time_stretch_max >= time_stretch) {
+ feedrate_limit = feedrate - (feedrate - feedrate_limit) * time_stretch / time_stretch_max;
+ done = true;
+ } else
+ time_stretch -= time_stretch_max;
+ for (auto it = adj; it != by_min_print_speed.end(); ++ it)
+ (*it)->slow_down_to_feedrate(feedrate_limit);
+ if (done)
+ return;
+ }
+ // Skip the other extruders with nearly the same min_print_speed, as they have been processed already.
+ auto next = adj;
+ for (++ next; next != by_min_print_speed.end() && (*next)->min_print_speed > (*adj)->min_print_speed - EPSILON; ++ next);
+ adj = next;
+ }
+ if (feedrate_next == 0.f)
+ // There are no other extrusions available for slow down.
+ break;
+ for (PerExtruderAdjustments *adj : by_min_print_speed) {
+ adj->idx_line_begin = adj->idx_line_end;
+ feedrate = feedrate_next;
+ }
+ }
+}
+
+// Calculate slow down for all the extruders.
+float CoolingBuffer::calculate_layer_slowdown(std::vector<PerExtruderAdjustments> &per_extruder_adjustments)
+{
+ // Sort the extruders by an increasing slowdown_below_layer_time.
+ // The layers with a lower slowdown_below_layer_time are slowed down
+ // together with all the other layers with slowdown_below_layer_time above.
+ std::vector<PerExtruderAdjustments*> by_slowdown_time;
+ by_slowdown_time.reserve(per_extruder_adjustments.size());
+ // Only insert entries, which are adjustable (have cooling enabled and non-zero stretchable time).
+ // Collect total print time of non-adjustable extruders.
+ float elapsed_time_total0 = 0.f;
+ for (PerExtruderAdjustments &adj : per_extruder_adjustments) {
+ // Curren total time for this extruder.
+ adj.time_total = adj.elapsed_time_total();
+ // Maximum time for this extruder, when all extrusion moves are slowed down to min_extrusion_speed.
+ adj.time_maximum = adj.maximum_time_after_slowdown(true);
+ if (adj.cooling_slow_down_enabled && adj.lines.size() > 0) {
+ by_slowdown_time.emplace_back(&adj);
+ if (! m_cooling_logic_proportional)
+ // sorts the lines, also sets adj.time_non_adjustable
+ adj.sort_lines_by_decreasing_feedrate();
+ } else
+ elapsed_time_total0 += adj.elapsed_time_total();
+ }
+ std::sort(by_slowdown_time.begin(), by_slowdown_time.end(),
+ [](const PerExtruderAdjustments *adj1, const PerExtruderAdjustments *adj2)
+ { return adj1->slowdown_below_layer_time < adj2->slowdown_below_layer_time; });
+
+ for (auto cur_begin = by_slowdown_time.begin(); cur_begin != by_slowdown_time.end(); ++ cur_begin) {
+ PerExtruderAdjustments &adj = *(*cur_begin);
+ // Calculate the current adjusted elapsed_time_total over the non-finalized extruders.
+ float total = elapsed_time_total0;
+ for (auto it = cur_begin; it != by_slowdown_time.end(); ++ it)
+ total += (*it)->time_total;
+ float slowdown_below_layer_time = adj.slowdown_below_layer_time * 1.001f;
+ if (total > slowdown_below_layer_time) {
+ // The current total time is above the minimum threshold of the rest of the extruders, don't adjust anything.
+ } else {
+ // Adjust this and all the following (higher config.slowdown_below_layer_time) extruders.
+ // Sum maximum slow down time as if everything was slowed down including the external perimeters.
+ float max_time = elapsed_time_total0;
+ for (auto it = cur_begin; it != by_slowdown_time.end(); ++ it)
+ max_time += (*it)->time_maximum;
+ if (max_time > slowdown_below_layer_time) {
+ if (m_cooling_logic_proportional)
+ extruder_range_slow_down_proportional(cur_begin, by_slowdown_time.end(), elapsed_time_total0, total, slowdown_below_layer_time);
+ else
+ extruder_range_slow_down_non_proportional(cur_begin, by_slowdown_time.end(), slowdown_below_layer_time - total);
+ } else {
+ // Slow down to maximum possible.
+ for (auto it = cur_begin; it != by_slowdown_time.end(); ++ it)
+ (*it)->slowdown_to_minimum_feedrate(true);
+ }
+ }
+ elapsed_time_total0 += adj.elapsed_time_total();
+ }
+
+ return elapsed_time_total0;
+}
+
+// Apply slow down over G-code lines stored in per_extruder_adjustments, enable fan if needed.
+// Returns the adjusted G-code.
+std::string CoolingBuffer::apply_layer_cooldown(
+ // Source G-code for the current layer.
+ const std::string &gcode,
+ // ID of the current layer, used to disable fan for the first n layers.
+ size_t layer_id,
+ // Total time of this layer after slow down, used to control the fan.
+ float layer_time,
+ // Per extruder list of G-code lines and their cool down attributes.
+ std::vector<PerExtruderAdjustments> &per_extruder_adjustments)
+{
+ // First sort the adjustment lines by of multiple extruders by their position in the source G-code.
+ std::vector<const CoolingLine*> lines;
+ {
+ size_t n_lines = 0;
+ for (const PerExtruderAdjustments &adj : per_extruder_adjustments)
+ n_lines += adj.lines.size();
+ lines.reserve(n_lines);
+ for (const PerExtruderAdjustments &adj : per_extruder_adjustments)
+ for (const CoolingLine &line : adj.lines)
+ lines.emplace_back(&line);
+ std::sort(lines.begin(), lines.end(), [](const CoolingLine *ln1, const CoolingLine *ln2) { return ln1->line_start < ln2->line_start; } );
+ }
+ // Second generate the adjusted G-code.
+ std::string new_gcode;
+ new_gcode.reserve(gcode.size() * 2);
+ int fan_speed = -1;
+ bool bridge_fan_control = false;
+ int bridge_fan_speed = 0;
+ auto change_extruder_set_fan = [ this, layer_id, layer_time, &new_gcode, &fan_speed, &bridge_fan_control, &bridge_fan_speed ]() {
+ const FullPrintConfig &config = m_gcodegen.config();
+#define EXTRUDER_CONFIG(OPT) config.OPT.get_at(m_current_extruder)
+ int min_fan_speed = EXTRUDER_CONFIG(min_fan_speed);
+ int fan_speed_new = EXTRUDER_CONFIG(fan_always_on) ? min_fan_speed : 0;
+ if (layer_id >= EXTRUDER_CONFIG(disable_fan_first_layers)) {
+ int max_fan_speed = EXTRUDER_CONFIG(max_fan_speed);
+ float slowdown_below_layer_time = float(EXTRUDER_CONFIG(slowdown_below_layer_time));
+ float fan_below_layer_time = float(EXTRUDER_CONFIG(fan_below_layer_time));
+ if (EXTRUDER_CONFIG(cooling)) {
+ if (layer_time < slowdown_below_layer_time) {
+ // Layer time very short. Enable the fan to a full throttle.
+ fan_speed_new = max_fan_speed;
+ } else if (layer_time < fan_below_layer_time) {
+ // Layer time quite short. Enable the fan proportionally according to the current layer time.
+ assert(layer_time >= slowdown_below_layer_time);
+ double t = (layer_time - slowdown_below_layer_time) / (fan_below_layer_time - slowdown_below_layer_time);
+ fan_speed_new = int(floor(t * min_fan_speed + (1. - t) * max_fan_speed) + 0.5);
+ }
+ }
+ bridge_fan_speed = EXTRUDER_CONFIG(bridge_fan_speed);
+#undef EXTRUDER_CONFIG
+ bridge_fan_control = bridge_fan_speed > fan_speed_new;
+ } else {
+ bridge_fan_control = false;
+ bridge_fan_speed = 0;
+ fan_speed_new = 0;
+ }
+ if (fan_speed_new != fan_speed) {
+ fan_speed = fan_speed_new;
+ new_gcode += m_gcodegen.writer().set_fan(fan_speed);
+ }
+ };
+
+ const char *pos = gcode.c_str();
+ int current_feedrate = 0;
+ const std::string toolchange_prefix = m_gcodegen.writer().toolchange_prefix();
+ change_extruder_set_fan();
+ for (const CoolingLine *line : lines) {
+ const char *line_start = gcode.c_str() + line->line_start;
+ const char *line_end = gcode.c_str() + line->line_end;
+ if (line_start > pos)
+ new_gcode.append(pos, line_start - pos);
+ if (line->type & CoolingLine::TYPE_SET_TOOL) {
+ unsigned int new_extruder = (unsigned int)atoi(line_start + toolchange_prefix.size());
+ if (new_extruder != m_current_extruder) {
+ m_current_extruder = new_extruder;
+ change_extruder_set_fan();
+ }
+ new_gcode.append(line_start, line_end - line_start);
+ } else if (line->type & CoolingLine::TYPE_BRIDGE_FAN_START) {
+ if (bridge_fan_control)
+ new_gcode += m_gcodegen.writer().set_fan(bridge_fan_speed, true);
+ } else if (line->type & CoolingLine::TYPE_BRIDGE_FAN_END) {
+ if (bridge_fan_control)
+ new_gcode += m_gcodegen.writer().set_fan(fan_speed, true);
+ } else if (line->type & CoolingLine::TYPE_EXTRUDE_END) {
+ // Just remove this comment.
+ } else if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE | CoolingLine::TYPE_HAS_F)) {
+ // Find the start of a comment, or roll to the end of line.
+ const char *end = line_start;
+ for (; end < line_end && *end != ';'; ++ end);
+ // Find the 'F' word.
+ const char *fpos = strstr(line_start + 2, " F") + 2;
+ int new_feedrate = current_feedrate;
+ bool modify = false;
+ assert(fpos != nullptr);
+ if (line->slowdown) {
+ modify = true;
+ new_feedrate = int(floor(60. * line->feedrate + 0.5));
+ } else {
+ new_feedrate = atoi(fpos);
+ if (new_feedrate != current_feedrate) {
+ // Append the line without the comment.
+ new_gcode.append(line_start, end - line_start);
+ current_feedrate = new_feedrate;
+ } else if ((line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) || line->length == 0.) {
+ // Feedrate does not change and this line does not move the print head. Skip the complete G-code line including the G-code comment.
+ end = line_end;
+ } else {
+ // Remove the feedrate from the G0/G1 line.
+ modify = true;
+ }
+ }
+ if (modify) {
+ if (new_feedrate != current_feedrate) {
+ // Replace the feedrate.
+ new_gcode.append(line_start, fpos - line_start);
+ current_feedrate = new_feedrate;
+ char buf[64];
+ sprintf(buf, "%d", int(current_feedrate));
+ new_gcode += buf;
+ } else {
+ // Remove the feedrate word.
+ const char *f = fpos;
+ // Roll the pointer before the 'F' word.
+ for (f -= 2; f > line_start && (*f == ' ' || *f == '\t'); -- f);
+ // Append up to the F word, without the trailing whitespace.
+ new_gcode.append(line_start, f - line_start + 1);
+ }
+ // Skip the non-whitespaces of the F parameter up the comment or end of line.
+ for (; fpos != end && *fpos != ' ' && *fpos != ';' && *fpos != '\n'; ++fpos);
+ // Append the rest of the line without the comment.
+ if (fpos < end)
+ new_gcode.append(fpos, end - fpos);
+ // There should never be an empty G1 statement emited by the filter. Such lines should be removed completely.
+ assert(new_gcode.size() < 4 || new_gcode.substr(new_gcode.size() - 4) != "G1 \n");
+ }
+ // Process the rest of the line.
+ if (end < line_end) {
+ if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) {
+ // Process comments, remove ";_EXTRUDE_SET_SPEED", ";_EXTERNAL_PERIMETER", ";_WIPE"
+ std::string comment(end, line_end);
+ boost::replace_all(comment, ";_EXTRUDE_SET_SPEED", "");
+ if (line->type & CoolingLine::TYPE_EXTERNAL_PERIMETER)
+ boost::replace_all(comment, ";_EXTERNAL_PERIMETER", "");
+ if (line->type & CoolingLine::TYPE_WIPE)
+ boost::replace_all(comment, ";_WIPE", "");
+ new_gcode += comment;
+ } else {
+ // Just attach the rest of the source line.
+ new_gcode.append(end, line_end - end);
+ }
+ }
+ } else {
+ new_gcode.append(line_start, line_end - line_start);
+ }
+ pos = line_end;
+ }
+ const char *gcode_end = gcode.c_str() + gcode.size();
+ if (pos < gcode_end)
+ new_gcode.append(pos, gcode_end - pos);
+
+ return new_gcode;
+}
+
+} // namespace Slic3r
diff --git a/src/libslic3r/GCode/CoolingBuffer.hpp b/src/libslic3r/GCode/CoolingBuffer.hpp
new file mode 100644
index 000000000..bf4b082e2
--- /dev/null
+++ b/src/libslic3r/GCode/CoolingBuffer.hpp
@@ -0,0 +1,53 @@
+#ifndef slic3r_CoolingBuffer_hpp_
+#define slic3r_CoolingBuffer_hpp_
+
+#include "libslic3r.h"
+#include <map>
+#include <string>
+
+namespace Slic3r {
+
+class GCode;
+class Layer;
+class PerExtruderAdjustments;
+
+// A standalone G-code filter, to control cooling of the print.
+// The G-code is processed per layer. Once a layer is collected, fan start / stop commands are edited
+// and the print is modified to stretch over a minimum layer time.
+//
+// The simple it sounds, the actual implementation is significantly more complex.
+// Namely, for a multi-extruder print, each material may require a different cooling logic.
+// For example, some materials may not like to print too slowly, while with some materials
+// we may slow down significantly.
+//
+class CoolingBuffer {
+public:
+ CoolingBuffer(GCode &gcodegen);
+ void reset();
+ void set_current_extruder(unsigned int extruder_id) { m_current_extruder = extruder_id; }
+ std::string process_layer(const std::string &gcode, size_t layer_id);
+ GCode* gcodegen() { return &m_gcodegen; }
+
+private:
+ CoolingBuffer& operator=(const CoolingBuffer&) = delete;
+ std::vector<PerExtruderAdjustments> parse_layer_gcode(const std::string &gcode, std::vector<float> &current_pos) const;
+ float calculate_layer_slowdown(std::vector<PerExtruderAdjustments> &per_extruder_adjustments);
+ // Apply slow down over G-code lines stored in per_extruder_adjustments, enable fan if needed.
+ // Returns the adjusted G-code.
+ std::string apply_layer_cooldown(const std::string &gcode, size_t layer_id, float layer_time, std::vector<PerExtruderAdjustments> &per_extruder_adjustments);
+
+ GCode& m_gcodegen;
+ std::string m_gcode;
+ // Internal data.
+ // X,Y,Z,E,F
+ std::vector<char> m_axis;
+ std::vector<float> m_current_pos;
+ unsigned int m_current_extruder;
+
+ // Old logic: proportional.
+ bool m_cooling_logic_proportional = false;
+};
+
+}
+
+#endif
diff --git a/src/libslic3r/GCode/PostProcessor.cpp b/src/libslic3r/GCode/PostProcessor.cpp
new file mode 100644
index 000000000..c04aeae3c
--- /dev/null
+++ b/src/libslic3r/GCode/PostProcessor.cpp
@@ -0,0 +1,60 @@
+#include "PostProcessor.hpp"
+
+#if 1
+//#ifdef WIN32
+
+namespace Slic3r {
+
+//FIXME Ignore until we include boost::process
+void run_post_process_scripts(const std::string &path, const PrintConfig &config)
+{
+}
+
+} // namespace Slic3r
+
+#else
+
+#include <boost/process/system.hpp>
+
+namespace Slic3r {
+
+void run_post_process_scripts(const std::string &path, const PrintConfig &config)
+{
+ if (config.post_process.values.empty())
+ return;
+ config.setenv_();
+ for (std::string script: config.post_process.values) {
+ // Ignore empty post processing script lines.
+ boost::trim(script);
+ if (script.empty())
+ continue;
+ BOOST_LOG_TRIVIAL(info) << "Executing script " << script << " on file " << path;
+ if (! boost::filesystem::exists(boost::filesystem::path(path)))
+ throw std::runtime_exception(std::string("The configured post-processing script does not exist: ") + path);
+#ifndef WIN32
+ file_status fs = boost::filesystem::status(path);
+ //FIXME test if executible by the effective UID / GID.
+ // throw std::runtime_exception(std::string("The configured post-processing script is not executable: check permissions. ") + path));
+#endif
+ int result = 0;
+#ifdef WIN32
+ if (boost::iends_with(file, ".gcode")) {
+ // The current process may be slic3r.exe or slic3r-console.exe.
+ // Find the path of the process:
+ wchar_t wpath_exe[_MAX_PATH + 1];
+ ::GetModuleFileNameW(nullptr, wpath_exe, _MAX_PATH);
+ boost::filesystem::path path_exe(wpath_exe);
+ // Replace it with the current perl interpreter.
+ result = boost::process::system((path_exe.parent_path() / "perl5.24.0.exe").string(), script, output_file);
+ } else
+#else
+ result = boost::process::system(script, output_file);
+#endif
+ if (result < 0)
+ BOOST_LOG_TRIVIAL(error) << "Script " << script << " on file " << path << " failed. Negative error code returned.";
+ }
+}
+
+} // namespace Slic3r
+
+#endif
diff --git a/src/libslic3r/GCode/PostProcessor.hpp b/src/libslic3r/GCode/PostProcessor.hpp
new file mode 100644
index 000000000..ce47374cb
--- /dev/null
+++ b/src/libslic3r/GCode/PostProcessor.hpp
@@ -0,0 +1,15 @@
+#ifndef slic3r_GCode_PostProcessor_hpp_
+#define slic3r_GCode_PostProcessor_hpp_
+
+#include <string>
+
+#include "../libslic3r.h"
+#include "../PrintConfig.hpp"
+
+namespace Slic3r {
+
+extern void run_post_process_scripts(const std::string &path, const PrintConfig &config);
+
+} // namespace Slic3r
+
+#endif /* slic3r_GCode_PostProcessor_hpp_ */
diff --git a/src/libslic3r/GCode/PressureEqualizer.cpp b/src/libslic3r/GCode/PressureEqualizer.cpp
new file mode 100644
index 000000000..3b2a58a88
--- /dev/null
+++ b/src/libslic3r/GCode/PressureEqualizer.cpp
@@ -0,0 +1,621 @@
+#include <memory.h>
+#include <string.h>
+#include <float.h>
+
+#include "../libslic3r.h"
+#include "../PrintConfig.hpp"
+
+#include "PressureEqualizer.hpp"
+
+namespace Slic3r {
+
+PressureEqualizer::PressureEqualizer(const Slic3r::GCodeConfig *config) :
+ m_config(config)
+{
+ reset();
+}
+
+PressureEqualizer::~PressureEqualizer()
+{
+}
+
+void PressureEqualizer::reset()
+{
+ circular_buffer_pos = 0;
+ circular_buffer_size = 100;
+ circular_buffer_items = 0;
+ circular_buffer.assign(circular_buffer_size, GCodeLine());
+
+ // Preallocate some data, so that output_buffer.data() will return an empty string.
+ output_buffer.assign(32, 0);
+ output_buffer_length = 0;
+
+ m_current_extruder = 0;
+ // Zero the position of the XYZE axes + the current feed
+ memset(m_current_pos, 0, sizeof(float) * 5);
+ m_current_extrusion_role = erNone;
+ // Expect the first command to fill the nozzle (deretract).
+ m_retracted = true;
+
+ // Calculate filamet crossections for the multiple extruders.
+ m_filament_crossections.clear();
+ for (size_t i = 0; i < m_config->filament_diameter.values.size(); ++ i) {
+ double r = m_config->filament_diameter.values[i];
+ double a = 0.25f*M_PI*r*r;
+ m_filament_crossections.push_back(float(a));
+ }
+
+ m_max_segment_length = 20.f;
+ // Volumetric rate of a 0.45mm x 0.2mm extrusion at 60mm/s XY movement: 0.45*0.2*60*60=5.4*60 = 324 mm^3/min
+ // Volumetric rate of a 0.45mm x 0.2mm extrusion at 20mm/s XY movement: 0.45*0.2*20*60=1.8*60 = 108 mm^3/min
+ // Slope of the volumetric rate, changing from 20mm/s to 60mm/s over 2 seconds: (5.4-1.8)*60*60/2=60*60*1.8 = 6480 mm^3/min^2 = 1.8 mm^3/s^2
+ m_max_volumetric_extrusion_rate_slope_positive = (m_config == NULL) ? 6480.f :
+ m_config->max_volumetric_extrusion_rate_slope_positive.value * 60.f * 60.f;
+ m_max_volumetric_extrusion_rate_slope_negative = (m_config == NULL) ? 6480.f :
+ m_config->max_volumetric_extrusion_rate_slope_negative.value * 60.f * 60.f;
+
+ for (size_t i = 0; i < numExtrusionRoles; ++ i) {
+ m_max_volumetric_extrusion_rate_slopes[i].negative = m_max_volumetric_extrusion_rate_slope_negative;
+ m_max_volumetric_extrusion_rate_slopes[i].positive = m_max_volumetric_extrusion_rate_slope_positive;
+ }
+
+ // Don't regulate the pressure in infill.
+ m_max_volumetric_extrusion_rate_slopes[erBridgeInfill].negative = 0;
+ m_max_volumetric_extrusion_rate_slopes[erBridgeInfill].positive = 0;
+ // Don't regulate the pressure in gap fill.
+ m_max_volumetric_extrusion_rate_slopes[erGapFill].negative = 0;
+ m_max_volumetric_extrusion_rate_slopes[erGapFill].positive = 0;
+
+ m_stat.reset();
+ line_idx = 0;
+}
+
+const char* PressureEqualizer::process(const char *szGCode, bool flush)
+{
+ // Reset length of the output_buffer.
+ output_buffer_length = 0;
+
+ if (szGCode != 0) {
+ const char *p = szGCode;
+ while (*p != 0) {
+ // Find end of the line.
+ const char *endl = p;
+ // Slic3r always generates end of lines in a Unix style.
+ for (; *endl != 0 && *endl != '\n'; ++ endl) ;
+ if (circular_buffer_items == circular_buffer_size)
+ // Buffer is full. Push out the oldest line.
+ output_gcode_line(circular_buffer[circular_buffer_pos]);
+ else
+ ++ circular_buffer_items;
+ // Process a G-code line, store it into the provided GCodeLine object.
+ size_t idx_tail = circular_buffer_pos;
+ circular_buffer_pos = circular_buffer_idx_next(circular_buffer_pos);
+ if (! process_line(p, endl - p, circular_buffer[idx_tail])) {
+ // The line has to be forgotten. It contains comment marks, which shall be
+ // filtered out of the target g-code.
+ circular_buffer_pos = idx_tail;
+ -- circular_buffer_items;
+ }
+ p = endl;
+ if (*p == '\n')
+ ++ p;
+ }
+ }
+
+ if (flush) {
+ // Flush the remaining valid lines of the circular buffer.
+ for (size_t idx = circular_buffer_idx_head(); circular_buffer_items > 0; -- circular_buffer_items) {
+ output_gcode_line(circular_buffer[idx]);
+ if (++ idx == circular_buffer_size)
+ idx = 0;
+ }
+ // Reset the index pointer.
+ assert(circular_buffer_items == 0);
+ circular_buffer_pos = 0;
+
+#if 1
+ printf("Statistics: \n");
+ printf("Minimum volumetric extrusion rate: %f\n", m_stat.volumetric_extrusion_rate_min);
+ printf("Maximum volumetric extrusion rate: %f\n", m_stat.volumetric_extrusion_rate_max);
+ if (m_stat.extrusion_length > 0)
+ m_stat.volumetric_extrusion_rate_avg /= m_stat.extrusion_length;
+ printf("Average volumetric extrusion rate: %f\n", m_stat.volumetric_extrusion_rate_avg);
+ m_stat.reset();
+#endif
+ }
+
+ return output_buffer.data();
+}
+
+// Is a white space?
+static inline bool is_ws(const char c) { return c == ' ' || c == '\t'; }
+// Is it an end of line? Consider a comment to be an end of line as well.
+static inline bool is_eol(const char c) { return c == 0 || c == '\r' || c == '\n' || c == ';'; };
+// Is it a white space or end of line?
+static inline bool is_ws_or_eol(const char c) { return is_ws(c) || is_eol(c); };
+
+// Eat whitespaces.
+static void eatws(const char *&line)
+{
+ while (is_ws(*line))
+ ++ line;
+}
+
+// Parse an int starting at the current position of a line.
+// If succeeded, the line pointer is advanced.
+static inline int parse_int(const char *&line)
+{
+ char *endptr = NULL;
+ long result = strtol(line, &endptr, 10);
+ if (endptr == NULL || !is_ws_or_eol(*endptr))
+ throw std::runtime_error("PressureEqualizer: Error parsing an int");
+ line = endptr;
+ return int(result);
+};
+
+// Parse an int starting at the current position of a line.
+// If succeeded, the line pointer is advanced.
+static inline float parse_float(const char *&line)
+{
+ char *endptr = NULL;
+ float result = strtof(line, &endptr);
+ if (endptr == NULL || !is_ws_or_eol(*endptr))
+ throw std::runtime_error("PressureEqualizer: Error parsing a float");
+ line = endptr;
+ return result;
+};
+
+#define EXTRUSION_ROLE_TAG ";_EXTRUSION_ROLE:"
+bool PressureEqualizer::process_line(const char *line, const size_t len, GCodeLine &buf)
+{
+ if (strncmp(line, EXTRUSION_ROLE_TAG, strlen(EXTRUSION_ROLE_TAG)) == 0) {
+ line += strlen(EXTRUSION_ROLE_TAG);
+ int role = atoi(line);
+ m_current_extrusion_role = ExtrusionRole(role);
+ ++ line_idx;
+ return false;
+ }
+
+ // Set the type, copy the line to the buffer.
+ buf.type = GCODELINETYPE_OTHER;
+ buf.modified = false;
+ if (buf.raw.size() < len + 1)
+ buf.raw.assign(line, line + len + 1);
+ else
+ memcpy(buf.raw.data(), line, len);
+ buf.raw[len] = 0;
+ buf.raw_length = len;
+
+ memcpy(buf.pos_start, m_current_pos, sizeof(float)*5);
+ memcpy(buf.pos_end, m_current_pos, sizeof(float)*5);
+ memset(buf.pos_provided, 0, 5);
+
+ buf.volumetric_extrusion_rate = 0.f;
+ buf.volumetric_extrusion_rate_start = 0.f;
+ buf.volumetric_extrusion_rate_end = 0.f;
+ buf.max_volumetric_extrusion_rate_slope_positive = 0.f;
+ buf.max_volumetric_extrusion_rate_slope_negative = 0.f;
+ buf.extrusion_role = m_current_extrusion_role;
+
+ // Parse the G-code line, store the result into the buf.
+ switch (toupper(*line ++)) {
+ case 'G': {
+ int gcode = parse_int(line);
+ eatws(line);
+ switch (gcode) {
+ case 0:
+ case 1:
+ {
+ // G0, G1: A FFF 3D printer does not make a difference between the two.
+ float new_pos[5];
+ memcpy(new_pos, m_current_pos, sizeof(float)*5);
+ bool changed[5] = { false, false, false, false, false };
+ while (!is_eol(*line)) {
+ char axis = toupper(*line++);
+ int i = -1;
+ switch (axis) {
+ case 'X':
+ case 'Y':
+ case 'Z':
+ i = axis - 'X';
+ break;
+ case 'E':
+ i = 3;
+ break;
+ case 'F':
+ i = 4;
+ break;
+ default:
+ assert(false);
+ }
+ if (i == -1)
+ throw std::runtime_error(std::string("GCode::PressureEqualizer: Invalid axis for G0/G1: ") + axis);
+ buf.pos_provided[i] = true;
+ new_pos[i] = parse_float(line);
+ if (i == 3 && m_config->use_relative_e_distances.value)
+ new_pos[i] += m_current_pos[i];
+ changed[i] = new_pos[i] != m_current_pos[i];
+ eatws(line);
+ }
+ if (changed[3]) {
+ // Extrusion, retract or unretract.
+ float diff = new_pos[3] - m_current_pos[3];
+ if (diff < 0) {
+ buf.type = GCODELINETYPE_RETRACT;
+ m_retracted = true;
+ } else if (! changed[0] && ! changed[1] && ! changed[2]) {
+ // assert(m_retracted);
+ buf.type = GCODELINETYPE_UNRETRACT;
+ m_retracted = false;
+ } else {
+ assert(changed[0] || changed[1]);
+ // Moving in XY plane.
+ buf.type = GCODELINETYPE_EXTRUDE;
+ // Calculate the volumetric extrusion rate.
+ float diff[4];
+ for (size_t i = 0; i < 4; ++ i)
+ diff[i] = new_pos[i] - m_current_pos[i];
+ // volumetric extrusion rate = A_filament * F_xyz * L_e / L_xyz [mm^3/min]
+ float len2 = diff[0]*diff[0]+diff[1]*diff[1]+diff[2]*diff[2];
+ float rate = m_filament_crossections[m_current_extruder] * new_pos[4] * sqrt((diff[3]*diff[3])/len2);
+ buf.volumetric_extrusion_rate = rate;
+ buf.volumetric_extrusion_rate_start = rate;
+ buf.volumetric_extrusion_rate_end = rate;
+ m_stat.update(rate, sqrt(len2));
+ if (rate < 40.f) {
+ printf("Extremely low flow rate: %f. Line %d, Length: %f, extrusion: %f Old position: (%f, %f, %f), new position: (%f, %f, %f)\n",
+ rate,
+ int(line_idx),
+ sqrt(len2), sqrt((diff[3]*diff[3])/len2),
+ m_current_pos[0], m_current_pos[1], m_current_pos[2],
+ new_pos[0], new_pos[1], new_pos[2]);
+ }
+ }
+ } else if (changed[0] || changed[1] || changed[2]) {
+ // Moving without extrusion.
+ buf.type = GCODELINETYPE_MOVE;
+ }
+ memcpy(m_current_pos, new_pos, sizeof(float) * 5);
+ break;
+ }
+ case 92:
+ {
+ // G92 : Set Position
+ // Set a logical coordinate position to a new value without actually moving the machine motors.
+ // Which axes to set?
+ bool set = false;
+ while (!is_eol(*line)) {
+ char axis = toupper(*line++);
+ switch (axis) {
+ case 'X':
+ case 'Y':
+ case 'Z':
+ m_current_pos[axis - 'X'] = (!is_ws_or_eol(*line)) ? parse_float(line) : 0.f;
+ set = true;
+ break;
+ case 'E':
+ m_current_pos[3] = (!is_ws_or_eol(*line)) ? parse_float(line) : 0.f;
+ set = true;
+ break;
+ default:
+ throw std::runtime_error(std::string("GCode::PressureEqualizer: Incorrect axis in a G92 G-code: ") + axis);
+ }
+ eatws(line);
+ }
+ assert(set);
+ break;
+ }
+ case 10:
+ case 22:
+ // Firmware retract.
+ buf.type = GCODELINETYPE_RETRACT;
+ m_retracted = true;
+ break;
+ case 11:
+ case 23:
+ // Firmware unretract.
+ buf.type = GCODELINETYPE_UNRETRACT;
+ m_retracted = false;
+ break;
+ default:
+ // Ignore the rest.
+ break;
+ }
+ break;
+ }
+ case 'M': {
+ int mcode = parse_int(line);
+ eatws(line);
+ switch (mcode) {
+ default:
+ // Ignore the rest of the M-codes.
+ break;
+ }
+ break;
+ }
+ case 'T':
+ {
+ // Activate an extruder head.
+ int new_extruder = parse_int(line);
+ if (new_extruder != m_current_extruder) {
+ m_current_extruder = new_extruder;
+ m_retracted = true;
+ buf.type = GCODELINETYPE_TOOL_CHANGE;
+ } else {
+ buf.type = GCODELINETYPE_NOOP;
+ }
+ break;
+ }
+ }
+
+ buf.extruder_id = m_current_extruder;
+ memcpy(buf.pos_end, m_current_pos, sizeof(float)*5);
+
+ adjust_volumetric_rate();
+ ++ line_idx;
+ return true;
+}
+
+void PressureEqualizer::output_gcode_line(GCodeLine &line)
+{
+ if (! line.modified) {
+ push_to_output(line.raw.data(), line.raw_length, true);
+ return;
+ }
+
+ // The line was modified.
+ // Find the comment.
+ const char *comment = line.raw.data();
+ while (*comment != ';' && *comment != 0) ++comment;
+ if (*comment != ';')
+ comment = NULL;
+
+ // Emit the line with lowered extrusion rates.
+ float l2 = line.dist_xyz2();
+ float l = sqrt(l2);
+ size_t nSegments = size_t(ceil(l / m_max_segment_length));
+ if (nSegments == 1) {
+ // Just update this segment.
+ push_line_to_output(line, line.feedrate() * line.volumetric_correction_avg(), comment);
+ } else {
+ bool accelerating = line.volumetric_extrusion_rate_start < line.volumetric_extrusion_rate_end;
+ // Update the initial and final feed rate values.
+ line.pos_start[4] = line.volumetric_extrusion_rate_start * line.pos_end[4] / line.volumetric_extrusion_rate;
+ line.pos_end [4] = line.volumetric_extrusion_rate_end * line.pos_end[4] / line.volumetric_extrusion_rate;
+ float feed_avg = 0.5f * (line.pos_start[4] + line.pos_end[4]);
+ // Limiting volumetric extrusion rate slope for this segment.
+ float max_volumetric_extrusion_rate_slope = accelerating ?
+ line.max_volumetric_extrusion_rate_slope_positive : line.max_volumetric_extrusion_rate_slope_negative;
+ // Total time for the segment, corrected for the possibly lowered volumetric feed rate,
+ // if accelerating / decelerating over the complete segment.
+ float t_total = line.dist_xyz() / feed_avg;
+ // Time of the acceleration / deceleration part of the segment, if accelerating / decelerating
+ // with the maximum volumetric extrusion rate slope.
+ float t_acc = 0.5f * (line.volumetric_extrusion_rate_start + line.volumetric_extrusion_rate_end) / max_volumetric_extrusion_rate_slope;
+ float l_acc = l;
+ float l_steady = 0.f;
+ if (t_acc < t_total) {
+ // One may achieve higher print speeds if part of the segment is not speed limited.
+ float l_acc = t_acc * feed_avg;
+ float l_steady = l - l_acc;
+ if (l_steady < 0.5f * m_max_segment_length) {
+ l_acc = l;
+ l_steady = 0.f;
+ } else
+ nSegments = size_t(ceil(l_acc / m_max_segment_length));
+ }
+ float pos_start[5];
+ float pos_end [5];
+ float pos_end2 [4];
+ memcpy(pos_start, line.pos_start, sizeof(float)*5);
+ memcpy(pos_end , line.pos_end , sizeof(float)*5);
+ if (l_steady > 0.f) {
+ // There will be a steady feed segment emitted.
+ if (accelerating) {
+ // Prepare the final steady feed rate segment.
+ memcpy(pos_end2, pos_end, sizeof(float)*4);
+ float t = l_acc / l;
+ for (int i = 0; i < 4; ++ i) {
+ pos_end[i] = pos_start[i] + (pos_end[i] - pos_start[i]) * t;
+ line.pos_provided[i] = true;
+ }
+ } else {
+ // Emit the steady feed rate segment.
+ float t = l_steady / l;
+ for (int i = 0; i < 4; ++ i) {
+ line.pos_end[i] = pos_start[i] + (pos_end[i] - pos_start[i]) * t;
+ line.pos_provided[i] = true;
+ }
+ push_line_to_output(line, pos_start[4], comment);
+ comment = NULL;
+ memcpy(line.pos_start, line.pos_end, sizeof(float)*5);
+ memcpy(pos_start, line.pos_end, sizeof(float)*5);
+ }
+ }
+ // Split the segment into pieces.
+ for (size_t i = 1; i < nSegments; ++ i) {
+ float t = float(i) / float(nSegments);
+ for (size_t j = 0; j < 4; ++ j) {
+ line.pos_end[j] = pos_start[j] + (pos_end[j] - pos_start[j]) * t;
+ line.pos_provided[j] = true;
+ }
+ // Interpolate the feed rate at the center of the segment.
+ push_line_to_output(line, pos_start[4] + (pos_end[4] - pos_start[4]) * (float(i) - 0.5f) / float(nSegments), comment);
+ comment = NULL;
+ memcpy(line.pos_start, line.pos_end, sizeof(float)*5);
+ }
+ if (l_steady > 0.f && accelerating) {
+ for (int i = 0; i < 4; ++ i) {
+ line.pos_end[i] = pos_end2[i];
+ line.pos_provided[i] = true;
+ }
+ push_line_to_output(line, pos_end[4], comment);
+ }
+ }
+}
+
+void PressureEqualizer::adjust_volumetric_rate()
+{
+ if (circular_buffer_items < 2)
+ return;
+
+ // Go back from the current circular_buffer_pos and lower the feedtrate to decrease the slope of the extrusion rate changes.
+ const size_t idx_head = circular_buffer_idx_head();
+ const size_t idx_tail = circular_buffer_idx_prev(circular_buffer_idx_tail());
+ size_t idx = idx_tail;
+ if (idx == idx_head || ! circular_buffer[idx].extruding())
+ // Nothing to do, the last move is not extruding.
+ return;
+
+ float feedrate_per_extrusion_role[numExtrusionRoles];
+ for (size_t i = 0; i < numExtrusionRoles; ++ i)
+ feedrate_per_extrusion_role[i] = FLT_MAX;
+ feedrate_per_extrusion_role[circular_buffer[idx].extrusion_role] = circular_buffer[idx].volumetric_extrusion_rate_start;
+
+ bool modified = true;
+ while (modified && idx != idx_head) {
+ size_t idx_prev = circular_buffer_idx_prev(idx);
+ for (; ! circular_buffer[idx_prev].extruding() && idx_prev != idx_head; idx_prev = circular_buffer_idx_prev(idx_prev)) ;
+ if (! circular_buffer[idx_prev].extruding())
+ break;
+ // Volumetric extrusion rate at the start of the succeding segment.
+ float rate_succ = circular_buffer[idx].volumetric_extrusion_rate_start;
+ // What is the gradient of the extrusion rate between idx_prev and idx?
+ idx = idx_prev;
+ GCodeLine &line = circular_buffer[idx];
+ for (size_t iRole = 1; iRole < numExtrusionRoles; ++ iRole) {
+ float rate_slope = m_max_volumetric_extrusion_rate_slopes[iRole].negative;
+ if (rate_slope == 0)
+ // The negative rate is unlimited.
+ continue;
+ float rate_end = feedrate_per_extrusion_role[iRole];
+ if (iRole == line.extrusion_role && rate_succ < rate_end)
+ // Limit by the succeeding volumetric flow rate.
+ rate_end = rate_succ;
+ if (line.volumetric_extrusion_rate_end > rate_end) {
+ line.volumetric_extrusion_rate_end = rate_end;
+ line.modified = true;
+ } else if (iRole == line.extrusion_role) {
+ rate_end = line.volumetric_extrusion_rate_end;
+ } else if (rate_end == FLT_MAX) {
+ // The rate for ExtrusionRole iRole is unlimited.
+ continue;
+ } else {
+ // Use the original, 'floating' extrusion rate as a starting point for the limiter.
+ }
+// modified = false;
+ float rate_start = rate_end + rate_slope * line.time_corrected();
+ if (rate_start < line.volumetric_extrusion_rate_start) {
+ // Limit the volumetric extrusion rate at the start of this segment due to a segment
+ // of ExtrusionType iRole, which will be extruded in the future.
+ line.volumetric_extrusion_rate_start = rate_start;
+ line.max_volumetric_extrusion_rate_slope_negative = rate_slope;
+ line.modified = true;
+// modified = true;
+ }
+ feedrate_per_extrusion_role[iRole] = (iRole == line.extrusion_role) ? line.volumetric_extrusion_rate_start : rate_start;
+ }
+ }
+
+ // Go forward and adjust the feedrate to decrease the slope of the extrusion rate changes.
+ for (size_t i = 0; i < numExtrusionRoles; ++ i)
+ feedrate_per_extrusion_role[i] = FLT_MAX;
+ feedrate_per_extrusion_role[circular_buffer[idx].extrusion_role] = circular_buffer[idx].volumetric_extrusion_rate_end;
+
+ assert(circular_buffer[idx].extruding());
+ while (idx != idx_tail) {
+ size_t idx_next = circular_buffer_idx_next(idx);
+ for (; ! circular_buffer[idx_next].extruding() && idx_next != idx_tail; idx_next = circular_buffer_idx_next(idx_next)) ;
+ if (! circular_buffer[idx_next].extruding())
+ break;
+ float rate_prec = circular_buffer[idx].volumetric_extrusion_rate_end;
+ // What is the gradient of the extrusion rate between idx_prev and idx?
+ idx = idx_next;
+ GCodeLine &line = circular_buffer[idx];
+ for (size_t iRole = 1; iRole < numExtrusionRoles; ++ iRole) {
+ float rate_slope = m_max_volumetric_extrusion_rate_slopes[iRole].positive;
+ if (rate_slope == 0)
+ // The positive rate is unlimited.
+ continue;
+ float rate_start = feedrate_per_extrusion_role[iRole];
+ if (iRole == line.extrusion_role && rate_prec < rate_start)
+ rate_start = rate_prec;
+ if (line.volumetric_extrusion_rate_start > rate_start) {
+ line.volumetric_extrusion_rate_start = rate_start;
+ line.modified = true;
+ } else if (iRole == line.extrusion_role) {
+ rate_start = line.volumetric_extrusion_rate_start;
+ } else if (rate_start == FLT_MAX) {
+ // The rate for ExtrusionRole iRole is unlimited.
+ continue;
+ } else {
+ // Use the original, 'floating' extrusion rate as a starting point for the limiter.
+ }
+ float rate_end = (rate_slope == 0) ? FLT_MAX : rate_start + rate_slope * line.time_corrected();
+ if (rate_end < line.volumetric_extrusion_rate_end) {
+ // Limit the volumetric extrusion rate at the start of this segment due to a segment
+ // of ExtrusionType iRole, which was extruded before.
+ line.volumetric_extrusion_rate_end = rate_end;
+ line.max_volumetric_extrusion_rate_slope_positive = rate_slope;
+ line.modified = true;
+ }
+ feedrate_per_extrusion_role[iRole] = (iRole == line.extrusion_role) ? line.volumetric_extrusion_rate_end : rate_end;
+ }
+ }
+}
+
+void PressureEqualizer::push_axis_to_output(const char axis, const float value, bool add_eol)
+{
+ char buf[2048];
+ int len = sprintf(buf,
+ (axis == 'E') ? " %c%.3f" : " %c%.5f",
+ axis, value);
+ push_to_output(buf, len, add_eol);
+}
+
+void PressureEqualizer::push_to_output(const char *text, const size_t len, bool add_eol)
+{
+ // New length of the output buffer content.
+ size_t len_new = output_buffer_length + len + 1;
+ if (add_eol)
+ ++ len_new;
+
+ // Resize the output buffer to a power of 2 higher than the required memory.
+ if (output_buffer.size() < len_new) {
+ size_t v = len_new;
+ // Compute the next highest power of 2 of 32-bit v
+ // http://graphics.stanford.edu/~seander/bithacks.html
+ v--;
+ v |= v >> 1;
+ v |= v >> 2;
+ v |= v >> 4;
+ v |= v >> 8;
+ v |= v >> 16;
+ v++;
+ output_buffer.resize(v);
+ }
+
+ // Copy the text to the output.
+ if (len != 0) {
+ memcpy(output_buffer.data() + output_buffer_length, text, len);
+ output_buffer_length += len;
+ }
+ if (add_eol)
+ output_buffer[output_buffer_length ++] = '\n';
+ output_buffer[output_buffer_length] = 0;
+}
+
+void PressureEqualizer::push_line_to_output(const GCodeLine &line, const float new_feedrate, const char *comment)
+{
+ push_to_output("G1", 2, false);
+ for (char i = 0; i < 3; ++ i)
+ if (line.pos_provided[i])
+ push_axis_to_output('X'+i, line.pos_end[i]);
+ push_axis_to_output('E', m_config->use_relative_e_distances.value ? (line.pos_end[3] - line.pos_start[3]) : line.pos_end[3]);
+// if (line.pos_provided[4] || fabs(line.feedrate() - new_feedrate) > 1e-5)
+ push_axis_to_output('F', new_feedrate);
+ // output comment and EOL
+ push_to_output(comment, (comment == NULL) ? 0 : strlen(comment), true);
+}
+
+} // namespace Slic3r
diff --git a/src/libslic3r/GCode/PressureEqualizer.hpp b/src/libslic3r/GCode/PressureEqualizer.hpp
new file mode 100644
index 000000000..13cdc9418
--- /dev/null
+++ b/src/libslic3r/GCode/PressureEqualizer.hpp
@@ -0,0 +1,212 @@
+#ifndef slic3r_GCode_PressureEqualizer_hpp_
+#define slic3r_GCode_PressureEqualizer_hpp_
+
+#include "../libslic3r.h"
+#include "../PrintConfig.hpp"
+#include "../ExtrusionEntity.hpp"
+
+namespace Slic3r {
+
+// Processes a G-code. Finds changes in the volumetric extrusion speed and adjusts the transitions
+// between these paths to limit fast changes in the volumetric extrusion speed.
+class PressureEqualizer
+{
+public:
+ PressureEqualizer(const Slic3r::GCodeConfig *config);
+ ~PressureEqualizer();
+
+ void reset();
+
+ // Process a next batch of G-code lines. Flush the internal buffers if asked for.
+ const char* process(const char *szGCode, bool flush);
+
+ size_t get_output_buffer_length() const { return output_buffer_length; }
+
+private:
+ struct Statistics
+ {
+ void reset() {
+ volumetric_extrusion_rate_min = std::numeric_limits<float>::max();
+ volumetric_extrusion_rate_max = 0.f;
+ volumetric_extrusion_rate_avg = 0.f;
+ extrusion_length = 0.f;
+ }
+ void update(float volumetric_extrusion_rate, float length) {
+ volumetric_extrusion_rate_min = std::min(volumetric_extrusion_rate_min, volumetric_extrusion_rate);
+ volumetric_extrusion_rate_max = std::max(volumetric_extrusion_rate_max, volumetric_extrusion_rate);
+ volumetric_extrusion_rate_avg += volumetric_extrusion_rate * length;
+ extrusion_length += length;
+ }
+ float volumetric_extrusion_rate_min;
+ float volumetric_extrusion_rate_max;
+ float volumetric_extrusion_rate_avg;
+ float extrusion_length;
+ };
+
+ struct Statistics m_stat;
+
+ // Keeps the reference, does not own the config.
+ const Slic3r::GCodeConfig *m_config;
+
+ // Private configuration values
+ // How fast could the volumetric extrusion rate increase / decrase? mm^3/sec^2
+ struct ExtrusionRateSlope {
+ float positive;
+ float negative;
+ };
+ enum { numExtrusionRoles = erSupportMaterialInterface + 1 };
+ ExtrusionRateSlope m_max_volumetric_extrusion_rate_slopes[numExtrusionRoles];
+ float m_max_volumetric_extrusion_rate_slope_positive;
+ float m_max_volumetric_extrusion_rate_slope_negative;
+ // Maximum segment length to split a long segment, if the initial and the final flow rate differ.
+ float m_max_segment_length;
+
+ // Configuration extracted from config.
+ // Area of the crossestion of each filament. Necessary to calculate the volumetric flow rate.
+ std::vector<float> m_filament_crossections;
+
+ // Internal data.
+ // X,Y,Z,E,F
+ float m_current_pos[5];
+ size_t m_current_extruder;
+ ExtrusionRole m_current_extrusion_role;
+ bool m_retracted;
+
+ enum GCodeLineType
+ {
+ GCODELINETYPE_INVALID,
+ GCODELINETYPE_NOOP,
+ GCODELINETYPE_OTHER,
+ GCODELINETYPE_RETRACT,
+ GCODELINETYPE_UNRETRACT,
+ GCODELINETYPE_TOOL_CHANGE,
+ GCODELINETYPE_MOVE,
+ GCODELINETYPE_EXTRUDE,
+ };
+
+ struct GCodeLine
+ {
+ GCodeLine() :
+ type(GCODELINETYPE_INVALID),
+ raw_length(0),
+ modified(false),
+ extruder_id(0),
+ volumetric_extrusion_rate(0.f),
+ volumetric_extrusion_rate_start(0.f),
+ volumetric_extrusion_rate_end(0.f)
+ {}
+
+ bool moving_xy() const { return fabs(pos_end[0] - pos_start[0]) > 0.f || fabs(pos_end[1] - pos_start[1]) > 0.f; }
+ bool moving_z () const { return fabs(pos_end[2] - pos_start[2]) > 0.f; }
+ bool extruding() const { return moving_xy() && pos_end[3] > pos_start[3]; }
+ bool retracting() const { return pos_end[3] < pos_start[3]; }
+ bool deretracting() const { return ! moving_xy() && pos_end[3] > pos_start[3]; }
+
+ float dist_xy2() const { return (pos_end[0] - pos_start[0]) * (pos_end[0] - pos_start[0]) + (pos_end[1] - pos_start[1]) * (pos_end[1] - pos_start[1]); }
+ float dist_xyz2() const { return (pos_end[0] - pos_start[0]) * (pos_end[0] - pos_start[0]) + (pos_end[1] - pos_start[1]) * (pos_end[1] - pos_start[1]) + (pos_end[2] - pos_start[2]) * (pos_end[2] - pos_start[2]); }
+ float dist_xy() const { return sqrt(dist_xy2()); }
+ float dist_xyz() const { return sqrt(dist_xyz2()); }
+ float dist_e() const { return fabs(pos_end[3] - pos_start[3]); }
+
+ float feedrate() const { return pos_end[4]; }
+ float time() const { return dist_xyz() / feedrate(); }
+ float time_inv() const { return feedrate() / dist_xyz(); }
+ float volumetric_correction_avg() const {
+ float avg_correction = 0.5f * (volumetric_extrusion_rate_start + volumetric_extrusion_rate_end) / volumetric_extrusion_rate;
+ assert(avg_correction > 0.f);
+ assert(avg_correction <= 1.00000001f);
+ return avg_correction;
+ }
+ float time_corrected() const { return time() * volumetric_correction_avg(); }
+
+ GCodeLineType type;
+
+ // We try to keep the string buffer once it has been allocated, so it will not be reallocated over and over.
+ std::vector<char> raw;
+ size_t raw_length;
+ // If modified, the raw text has to be adapted by the new extrusion rate,
+ // or maybe the line needs to be split into multiple lines.
+ bool modified;
+
+ // float timeStart;
+ // float timeEnd;
+ // X,Y,Z,E,F. Storing the state of the currently active extruder only.
+ float pos_start[5];
+ float pos_end[5];
+ // Was the axis found on the G-code line? X,Y,Z,F
+ bool pos_provided[5];
+
+ // Index of the active extruder.
+ size_t extruder_id;
+ // Extrusion role of this segment.
+ ExtrusionRole extrusion_role;
+
+ // Current volumetric extrusion rate.
+ float volumetric_extrusion_rate;
+ // Volumetric extrusion rate at the start of this segment.
+ float volumetric_extrusion_rate_start;
+ // Volumetric extrusion rate at the end of this segment.
+ float volumetric_extrusion_rate_end;
+
+ // Volumetric extrusion rate slope limiting this segment.
+ // If set to zero, the slope is unlimited.
+ float max_volumetric_extrusion_rate_slope_positive;
+ float max_volumetric_extrusion_rate_slope_negative;
+ };
+
+ // Circular buffer of GCode lines. The circular buffer size will be limited to circular_buffer_size.
+ std::vector<GCodeLine> circular_buffer;
+ // Current position of the circular buffer (index, where to write the next line to, the line has to be pushed out before it is overwritten).
+ size_t circular_buffer_pos;
+ // Circular buffer size, configuration value.
+ size_t circular_buffer_size;
+ // Number of valid lines in the circular buffer. Lower or equal to circular_buffer_size.
+ size_t circular_buffer_items;
+
+ // Output buffer will only grow. It will not be reallocated over and over.
+ std::vector<char> output_buffer;
+ size_t output_buffer_length;
+
+ // For debugging purposes. Index of the G-code line processed.
+ size_t line_idx;
+
+ bool process_line(const char *line, const size_t len, GCodeLine &buf);
+ void output_gcode_line(GCodeLine &buf);
+
+ // Go back from the current circular_buffer_pos and lower the feedtrate to decrease the slope of the extrusion rate changes.
+ // Then go forward and adjust the feedrate to decrease the slope of the extrusion rate changes.
+ void adjust_volumetric_rate();
+
+ // Push the text to the end of the output_buffer.
+ void push_to_output(const char *text, const size_t len, bool add_eol = true);
+ // Push an axis assignment to the end of the output buffer.
+ void push_axis_to_output(const char axis, const float value, bool add_eol = false);
+ // Push a G-code line to the output,
+ void push_line_to_output(const GCodeLine &line, const float new_feedrate, const char *comment);
+
+ size_t circular_buffer_idx_head() const {
+ size_t idx = circular_buffer_pos + circular_buffer_size - circular_buffer_items;
+ if (idx >= circular_buffer_size)
+ idx -= circular_buffer_size;
+ return idx;
+ }
+
+ size_t circular_buffer_idx_tail() const { return circular_buffer_pos; }
+
+ size_t circular_buffer_idx_prev(size_t idx) const {
+ idx += circular_buffer_size - 1;
+ if (idx >= circular_buffer_size)
+ idx -= circular_buffer_size;
+ return idx;
+ }
+
+ size_t circular_buffer_idx_next(size_t idx) const {
+ if (++ idx >= circular_buffer_size)
+ idx -= circular_buffer_size;
+ return idx;
+ }
+};
+
+} // namespace Slic3r
+
+#endif /* slic3r_GCode_PressureEqualizer_hpp_ */
diff --git a/src/libslic3r/GCode/PreviewData.cpp b/src/libslic3r/GCode/PreviewData.cpp
new file mode 100644
index 000000000..9cf9716e0
--- /dev/null
+++ b/src/libslic3r/GCode/PreviewData.cpp
@@ -0,0 +1,456 @@
+#include "Analyzer.hpp"
+#include "PreviewData.hpp"
+#include <float.h>
+#include <wx/intl.h>
+#include <I18N.hpp>
+
+#include <boost/format.hpp>
+
+//! macro used to mark string used at localization,
+#define L(s) (s)
+
+namespace Slic3r {
+
+const GCodePreviewData::Color GCodePreviewData::Color::Dummy(0.0f, 0.0f, 0.0f, 0.0f);
+
+GCodePreviewData::Color::Color()
+{
+ rgba[0] = 1.0f;
+ rgba[1] = 1.0f;
+ rgba[2] = 1.0f;
+ rgba[3] = 1.0f;
+}
+
+GCodePreviewData::Color::Color(float r, float g, float b, float a)
+{
+ rgba[0] = r;
+ rgba[1] = g;
+ rgba[2] = b;
+ rgba[3] = a;
+}
+
+std::vector<unsigned char> GCodePreviewData::Color::as_bytes() const
+{
+ std::vector<unsigned char> ret;
+ for (unsigned int i = 0; i < 4; ++i)
+ {
+ ret.push_back((unsigned char)(255.0f * rgba[i]));
+ }
+ return ret;
+}
+
+GCodePreviewData::Extrusion::Layer::Layer(float z, const ExtrusionPaths& paths)
+ : z(z)
+ , paths(paths)
+{
+}
+
+GCodePreviewData::Travel::Polyline::Polyline(EType type, EDirection direction, float feedrate, unsigned int extruder_id, const Polyline3& polyline)
+ : type(type)
+ , direction(direction)
+ , feedrate(feedrate)
+ , extruder_id(extruder_id)
+ , polyline(polyline)
+{
+}
+
+const GCodePreviewData::Color GCodePreviewData::Range::Default_Colors[Colors_Count] =
+{
+ Color(0.043f, 0.173f, 0.478f, 1.0f),
+ Color(0.075f, 0.349f, 0.522f, 1.0f),
+ Color(0.110f, 0.533f, 0.569f, 1.0f),
+ Color(0.016f, 0.839f, 0.059f, 1.0f),
+ Color(0.667f, 0.949f, 0.000f, 1.0f),
+ Color(0.988f, 0.975f, 0.012f, 1.0f),
+ Color(0.961f, 0.808f, 0.039f, 1.0f),
+ Color(0.890f, 0.533f, 0.125f, 1.0f),
+ Color(0.820f, 0.408f, 0.188f, 1.0f),
+ Color(0.761f, 0.322f, 0.235f, 1.0f)
+};
+
+GCodePreviewData::Range::Range()
+{
+ reset();
+}
+
+void GCodePreviewData::Range::reset()
+{
+ min = FLT_MAX;
+ max = -FLT_MAX;
+}
+
+bool GCodePreviewData::Range::empty() const
+{
+ return min == max;
+}
+
+void GCodePreviewData::Range::update_from(float value)
+{
+ min = std::min(min, value);
+ max = std::max(max, value);
+}
+
+void GCodePreviewData::Range::update_from(const Range& other)
+{
+ min = std::min(min, other.min);
+ max = std::max(max, other.max);
+}
+
+void GCodePreviewData::Range::set_from(const Range& other)
+{
+ min = other.min;
+ max = other.max;
+}
+
+float GCodePreviewData::Range::step_size() const
+{
+ return (max - min) / (float)(Colors_Count - 1);
+}
+
+GCodePreviewData::Color GCodePreviewData::Range::get_color_at(float value) const
+{
+ if (empty())
+ return Color::Dummy;
+
+ float global_t = (value - min) / step_size();
+
+ unsigned int low = (unsigned int)global_t;
+ unsigned int high = clamp((unsigned int)0, Colors_Count - 1, low + 1);
+
+ Color color_low = colors[low];
+ Color color_high = colors[high];
+
+ float local_t = global_t - (float)low;
+
+ // interpolate in RGB space
+ Color ret;
+ for (unsigned int i = 0; i < 4; ++i)
+ {
+ ret.rgba[i] = lerp(color_low.rgba[i], color_high.rgba[i], local_t);
+ }
+ return ret;
+}
+
+GCodePreviewData::LegendItem::LegendItem(const std::string& text, const GCodePreviewData::Color& color)
+ : text(text)
+ , color(color)
+{
+}
+
+const GCodePreviewData::Color GCodePreviewData::Extrusion::Default_Extrusion_Role_Colors[Num_Extrusion_Roles] =
+{
+ Color(0.0f, 0.0f, 0.0f, 1.0f), // erNone
+ Color(1.0f, 0.0f, 0.0f, 1.0f), // erPerimeter
+ Color(0.0f, 1.0f, 0.0f, 1.0f), // erExternalPerimeter
+ Color(0.0f, 0.0f, 1.0f, 1.0f), // erOverhangPerimeter
+ Color(1.0f, 1.0f, 0.0f, 1.0f), // erInternalInfill
+ Color(1.0f, 0.0f, 1.0f, 1.0f), // erSolidInfill
+ Color(0.0f, 1.0f, 1.0f, 1.0f), // erTopSolidInfill
+ Color(0.5f, 0.5f, 0.5f, 1.0f), // erBridgeInfill
+ Color(1.0f, 1.0f, 1.0f, 1.0f), // erGapFill
+ Color(0.5f, 0.0f, 0.0f, 1.0f), // erSkirt
+ Color(0.0f, 0.5f, 0.0f, 1.0f), // erSupportMaterial
+ Color(0.0f, 0.0f, 0.5f, 1.0f), // erSupportMaterialInterface
+ Color(0.7f, 0.89f, 0.67f, 1.0f), // erWipeTower
+ Color(1.0f, 1.0f, 0.0f, 1.0f), // erCustom
+ Color(0.0f, 0.0f, 0.0f, 1.0f) // erMixed
+};
+
+// todo: merge with Slic3r::ExtrusionRole2String() from GCode.cpp
+const std::string GCodePreviewData::Extrusion::Default_Extrusion_Role_Names[Num_Extrusion_Roles]
+{
+ L("None"),
+ L("Perimeter"),
+ L("External perimeter"),
+ L("Overhang perimeter"),
+ L("Internal infill"),
+ L("Solid infill"),
+ L("Top solid infill"),
+ L("Bridge infill"),
+ L("Gap fill"),
+ L("Skirt"),
+ L("Support material"),
+ L("Support material interface"),
+ L("Wipe tower"),
+ L("Custom"),
+ L("Mixed")
+};
+
+const GCodePreviewData::Extrusion::EViewType GCodePreviewData::Extrusion::Default_View_Type = GCodePreviewData::Extrusion::FeatureType;
+
+void GCodePreviewData::Extrusion::set_default()
+{
+ view_type = Default_View_Type;
+
+ ::memcpy((void*)role_colors, (const void*)Default_Extrusion_Role_Colors, Num_Extrusion_Roles * sizeof(Color));
+
+ for (unsigned int i = 0; i < Num_Extrusion_Roles; ++i)
+ {
+ role_names[i] = Default_Extrusion_Role_Names[i];
+ }
+
+ role_flags = 0;
+ for (unsigned int i = 0; i < Num_Extrusion_Roles; ++i)
+ {
+ role_flags |= 1 << i;
+ }
+}
+
+bool GCodePreviewData::Extrusion::is_role_flag_set(ExtrusionRole role) const
+{
+ return is_role_flag_set(role_flags, role);
+}
+
+bool GCodePreviewData::Extrusion::is_role_flag_set(unsigned int flags, ExtrusionRole role)
+{
+ return GCodeAnalyzer::is_valid_extrusion_role(role) && (flags & (1 << (role - erPerimeter))) != 0;
+}
+
+const float GCodePreviewData::Travel::Default_Width = 0.075f;
+const float GCodePreviewData::Travel::Default_Height = 0.075f;
+const GCodePreviewData::Color GCodePreviewData::Travel::Default_Type_Colors[Num_Types] =
+{
+ Color(0.0f, 0.0f, 0.75f, 1.0f), // Move
+ Color(0.0f, 0.75f, 0.0f, 1.0f), // Extrude
+ Color(0.75f, 0.0f, 0.0f, 1.0f), // Retract
+};
+
+void GCodePreviewData::Travel::set_default()
+{
+ width = Default_Width;
+ height = Default_Height;
+ ::memcpy((void*)type_colors, (const void*)Default_Type_Colors, Num_Types * sizeof(Color));
+
+ is_visible = false;
+}
+
+const GCodePreviewData::Color GCodePreviewData::Retraction::Default_Color = GCodePreviewData::Color(1.0f, 1.0f, 1.0f, 1.0f);
+
+GCodePreviewData::Retraction::Position::Position(const Vec3crd& position, float width, float height)
+ : position(position)
+ , width(width)
+ , height(height)
+{
+}
+
+void GCodePreviewData::Retraction::set_default()
+{
+ color = Default_Color;
+ is_visible = false;
+}
+
+void GCodePreviewData::Shell::set_default()
+{
+ is_visible = false;
+}
+
+GCodePreviewData::GCodePreviewData()
+{
+ set_default();
+}
+
+void GCodePreviewData::set_default()
+{
+ ::memcpy((void*)ranges.height.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color));
+ ::memcpy((void*)ranges.width.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color));
+ ::memcpy((void*)ranges.feedrate.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color));
+ ::memcpy((void*)ranges.volumetric_rate.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color));
+
+ extrusion.set_default();
+ travel.set_default();
+ retraction.set_default();
+ unretraction.set_default();
+ shell.set_default();
+}
+
+void GCodePreviewData::reset()
+{
+ ranges.width.reset();
+ ranges.height.reset();
+ ranges.feedrate.reset();
+ ranges.volumetric_rate.reset();
+ extrusion.layers.clear();
+ travel.polylines.clear();
+ retraction.positions.clear();
+ unretraction.positions.clear();
+}
+
+bool GCodePreviewData::empty() const
+{
+ return extrusion.layers.empty() && travel.polylines.empty() && retraction.positions.empty() && unretraction.positions.empty();
+}
+
+GCodePreviewData::Color GCodePreviewData::get_extrusion_role_color(ExtrusionRole role) const
+{
+ return extrusion.role_colors[role];
+}
+
+GCodePreviewData::Color GCodePreviewData::get_height_color(float height) const
+{
+ return ranges.height.get_color_at(height);
+}
+
+GCodePreviewData::Color GCodePreviewData::get_width_color(float width) const
+{
+ return ranges.width.get_color_at(width);
+}
+
+GCodePreviewData::Color GCodePreviewData::get_feedrate_color(float feedrate) const
+{
+ return ranges.feedrate.get_color_at(feedrate);
+}
+
+GCodePreviewData::Color GCodePreviewData::get_volumetric_rate_color(float rate) const
+{
+ return ranges.volumetric_rate.get_color_at(rate);
+}
+
+void GCodePreviewData::set_extrusion_role_color(const std::string& role_name, float red, float green, float blue, float alpha)
+{
+ for (unsigned int i = 0; i < Extrusion::Num_Extrusion_Roles; ++i)
+ {
+ if (role_name == extrusion.role_names[i])
+ {
+ extrusion.role_colors[i] = Color(red, green, blue, alpha);
+ break;
+ }
+ }
+}
+
+void GCodePreviewData::set_extrusion_paths_colors(const std::vector<std::string>& colors)
+{
+ unsigned int size = (unsigned int)colors.size();
+
+ if (size % 2 != 0)
+ return;
+
+ for (unsigned int i = 0; i < size; i += 2)
+ {
+ const std::string& color_str = colors[i + 1];
+
+ if (color_str.size() == 6)
+ {
+ bool valid = true;
+ for (int c = 0; c < 6; ++c)
+ {
+ if (::isxdigit(color_str[c]) == 0)
+ {
+ valid = false;
+ break;
+ }
+ }
+
+ if (valid)
+ {
+ unsigned int color;
+ std::stringstream ss;
+ ss << std::hex << color_str;
+ ss >> color;
+
+ float den = 1.0f / 255.0f;
+
+ float r = (float)((color & 0xFF0000) >> 16) * den;
+ float g = (float)((color & 0x00FF00) >> 8) * den;
+ float b = (float)(color & 0x0000FF) * den;
+
+ this->set_extrusion_role_color(colors[i], r, g, b, 1.0f);
+ }
+ }
+ }
+}
+
+std::string GCodePreviewData::get_legend_title() const
+{
+ switch (extrusion.view_type)
+ {
+ case Extrusion::FeatureType:
+ return L("Feature type");
+ case Extrusion::Height:
+ return L("Height (mm)");
+ case Extrusion::Width:
+ return L("Width (mm)");
+ case Extrusion::Feedrate:
+ return L("Speed (mm/s)");
+ case Extrusion::VolumetricRate:
+ return L("Volumetric flow rate (mm3/s)");
+ case Extrusion::Tool:
+ return L("Tool");
+ }
+
+ return "";
+}
+
+GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std::vector<float>& tool_colors) const
+{
+ struct Helper
+ {
+ static void FillListFromRange(LegendItemsList& list, const Range& range, unsigned int decimals, float scale_factor)
+ {
+ list.reserve(Range::Colors_Count);
+
+ float step = range.step_size();
+ for (int i = Range::Colors_Count - 1; i >= 0; --i)
+ {
+ char buf[1024];
+ sprintf(buf, "%.*f", decimals, scale_factor * (range.min + (float)i * step));
+ list.emplace_back(buf, range.colors[i]);
+ }
+ }
+ };
+
+ LegendItemsList items;
+
+ switch (extrusion.view_type)
+ {
+ case Extrusion::FeatureType:
+ {
+ ExtrusionRole first_valid = erPerimeter;
+ ExtrusionRole last_valid = erCustom;
+
+ items.reserve(last_valid - first_valid + 1);
+ for (unsigned int i = (unsigned int)first_valid; i <= (unsigned int)last_valid; ++i)
+ {
+ items.emplace_back(Slic3r::I18N::translate(extrusion.role_names[i]), extrusion.role_colors[i]);
+ }
+
+ break;
+ }
+ case Extrusion::Height:
+ {
+ Helper::FillListFromRange(items, ranges.height, 3, 1.0f);
+ break;
+ }
+ case Extrusion::Width:
+ {
+ Helper::FillListFromRange(items, ranges.width, 3, 1.0f);
+ break;
+ }
+ case Extrusion::Feedrate:
+ {
+ Helper::FillListFromRange(items, ranges.feedrate, 1, 1.0f);
+ break;
+ }
+ case Extrusion::VolumetricRate:
+ {
+ Helper::FillListFromRange(items, ranges.volumetric_rate, 3, 1.0f);
+ break;
+ }
+ case Extrusion::Tool:
+ {
+ unsigned int tools_colors_count = tool_colors.size() / 4;
+ items.reserve(tools_colors_count);
+ for (unsigned int i = 0; i < tools_colors_count; ++i)
+ {
+ GCodePreviewData::Color color;
+ ::memcpy((void*)color.rgba, (const void*)(tool_colors.data() + i * 4), 4 * sizeof(float));
+ items.emplace_back((boost::format(Slic3r::I18N::translate(L("Extruder %d"))) % (i + 1)).str(), color);
+ }
+
+ break;
+ }
+ }
+
+ return items;
+}
+
+} // namespace Slic3r
diff --git a/src/libslic3r/GCode/PreviewData.hpp b/src/libslic3r/GCode/PreviewData.hpp
new file mode 100644
index 000000000..ab74993f5
--- /dev/null
+++ b/src/libslic3r/GCode/PreviewData.hpp
@@ -0,0 +1,208 @@
+#ifndef slic3r_GCode_PreviewData_hpp_
+#define slic3r_GCode_PreviewData_hpp_
+
+#include "../libslic3r.h"
+#include "../ExtrusionEntity.hpp"
+
+#include "Point.hpp"
+
+namespace Slic3r {
+
+class GCodePreviewData
+{
+public:
+ struct Color
+ {
+ float rgba[4];
+
+ Color();
+ Color(float r, float g, float b, float a);
+
+ std::vector<unsigned char> as_bytes() const;
+
+ static const Color Dummy;
+ };
+
+ struct Range
+ {
+ static const unsigned int Colors_Count = 10;
+ static const Color Default_Colors[Colors_Count];
+
+ Color colors[Colors_Count];
+ float min;
+ float max;
+
+ Range();
+
+ void reset();
+ bool empty() const;
+ void update_from(float value);
+ void update_from(const Range& other);
+ void set_from(const Range& other);
+ float step_size() const;
+
+ Color get_color_at(float value) const;
+ };
+
+ struct Ranges
+ {
+ Range height;
+ Range width;
+ Range feedrate;
+ Range volumetric_rate;
+ };
+
+ struct LegendItem
+ {
+ std::string text;
+ Color color;
+
+ LegendItem(const std::string& text, const Color& color);
+ };
+
+ typedef std::vector<LegendItem> LegendItemsList;
+
+ struct Extrusion
+ {
+ enum EViewType : unsigned char
+ {
+ FeatureType,
+ Height,
+ Width,
+ Feedrate,
+ VolumetricRate,
+ Tool,
+ Num_View_Types
+ };
+
+ static const unsigned int Num_Extrusion_Roles = (unsigned int)erMixed + 1;
+ static const Color Default_Extrusion_Role_Colors[Num_Extrusion_Roles];
+ static const std::string Default_Extrusion_Role_Names[Num_Extrusion_Roles];
+ static const EViewType Default_View_Type;
+
+ struct Layer
+ {
+ float z;
+ ExtrusionPaths paths;
+
+ Layer(float z, const ExtrusionPaths& paths);
+ };
+
+ typedef std::vector<Layer> LayersList;
+
+ EViewType view_type;
+ Color role_colors[Num_Extrusion_Roles];
+ std::string role_names[Num_Extrusion_Roles];
+ LayersList layers;
+ unsigned int role_flags;
+
+ void set_default();
+ bool is_role_flag_set(ExtrusionRole role) const;
+
+ static bool is_role_flag_set(unsigned int flags, ExtrusionRole role);
+ };
+
+ struct Travel
+ {
+ enum EType : unsigned char
+ {
+ Move,
+ Extrude,
+ Retract,
+ Num_Types
+ };
+
+ static const float Default_Width;
+ static const float Default_Height;
+ static const Color Default_Type_Colors[Num_Types];
+
+ struct Polyline
+ {
+ enum EDirection
+ {
+ Vertical,
+ Generic,
+ Num_Directions
+ };
+
+ EType type;
+ EDirection direction;
+ float feedrate;
+ unsigned int extruder_id;
+ Polyline3 polyline;
+
+ Polyline(EType type, EDirection direction, float feedrate, unsigned int extruder_id, const Polyline3& polyline);
+ };
+
+ typedef std::vector<Polyline> PolylinesList;
+
+ PolylinesList polylines;
+ float width;
+ float height;
+ Color type_colors[Num_Types];
+ bool is_visible;
+
+ void set_default();
+ };
+
+ struct Retraction
+ {
+ static const Color Default_Color;
+
+ struct Position
+ {
+ Vec3crd position;
+ float width;
+ float height;
+
+ Position(const Vec3crd& position, float width, float height);
+ };
+
+ typedef std::vector<Position> PositionsList;
+
+ PositionsList positions;
+ Color color;
+ bool is_visible;
+
+ void set_default();
+ };
+
+ struct Shell
+ {
+ bool is_visible;
+
+ void set_default();
+ };
+
+ Extrusion extrusion;
+ Travel travel;
+ Retraction retraction;
+ Retraction unretraction;
+ Shell shell;
+ Ranges ranges;
+
+ GCodePreviewData();
+
+ void set_default();
+ void reset();
+ bool empty() const;
+
+ Color get_extrusion_role_color(ExtrusionRole role) const;
+ Color get_height_color(float height) const;
+ Color get_width_color(float width) const;
+ Color get_feedrate_color(float feedrate) const;
+ Color get_volumetric_rate_color(float rate) const;
+
+ void set_extrusion_role_color(const std::string& role_name, float red, float green, float blue, float alpha);
+ void set_extrusion_paths_colors(const std::vector<std::string>& colors);
+
+ std::string get_legend_title() const;
+ LegendItemsList get_legend_items(const std::vector<float>& tool_colors) const;
+};
+
+GCodePreviewData::Color operator + (const GCodePreviewData::Color& c1, const GCodePreviewData::Color& c2);
+GCodePreviewData::Color operator * (float f, const GCodePreviewData::Color& color);
+
+} // namespace Slic3r
+
+#endif /* slic3r_GCode_PreviewData_hpp_ */
diff --git a/src/libslic3r/GCode/PrintExtents.cpp b/src/libslic3r/GCode/PrintExtents.cpp
new file mode 100644
index 000000000..92a58fdf0
--- /dev/null
+++ b/src/libslic3r/GCode/PrintExtents.cpp
@@ -0,0 +1,186 @@
+// Calculate extents of the extrusions assigned to Print / PrintObject.
+// The extents are used for assessing collisions of the print with the priming towers,
+// to decide whether to pause the print after the priming towers are extruded
+// to let the operator remove them from the print bed.
+
+#include "../BoundingBox.hpp"
+#include "../ExtrusionEntity.hpp"
+#include "../ExtrusionEntityCollection.hpp"
+#include "../Print.hpp"
+
+#include "PrintExtents.hpp"
+#include "WipeTower.hpp"
+
+namespace Slic3r {
+
+static inline BoundingBox extrusion_polyline_extents(const Polyline &polyline, const coord_t radius)
+{
+ BoundingBox bbox;
+ if (! polyline.points.empty())
+ bbox.merge(polyline.points.front());
+ for (const Point &pt : polyline.points) {
+ bbox.min(0) = std::min(bbox.min(0), pt(0) - radius);
+ bbox.min(1) = std::min(bbox.min(1), pt(1) - radius);
+ bbox.max(0) = std::max(bbox.max(0), pt(0) + radius);
+ bbox.max(1) = std::max(bbox.max(1), pt(1) + radius);
+ }
+ return bbox;
+}
+
+static inline BoundingBoxf extrusionentity_extents(const ExtrusionPath &extrusion_path)
+{
+ BoundingBox bbox = extrusion_polyline_extents(extrusion_path.polyline, scale_(0.5 * extrusion_path.width));
+ BoundingBoxf bboxf;
+ if (! empty(bbox)) {
+ bboxf.min = unscale(bbox.min);
+ bboxf.max = unscale(bbox.max);
+ bboxf.defined = true;
+ }
+ return bboxf;
+}
+
+static inline BoundingBoxf extrusionentity_extents(const ExtrusionLoop &extrusion_loop)
+{
+ BoundingBox bbox;
+ for (const ExtrusionPath &extrusion_path : extrusion_loop.paths)
+ bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, scale_(0.5 * extrusion_path.width)));
+ BoundingBoxf bboxf;
+ if (! empty(bbox)) {
+ bboxf.min = unscale(bbox.min);
+ bboxf.max = unscale(bbox.max);
+ bboxf.defined = true;
+ }
+ return bboxf;
+}
+
+static inline BoundingBoxf extrusionentity_extents(const ExtrusionMultiPath &extrusion_multi_path)
+{
+ BoundingBox bbox;
+ for (const ExtrusionPath &extrusion_path : extrusion_multi_path.paths)
+ bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, scale_(0.5 * extrusion_path.width)));
+ BoundingBoxf bboxf;
+ if (! empty(bbox)) {
+ bboxf.min = unscale(bbox.min);
+ bboxf.max = unscale(bbox.max);
+ bboxf.defined = true;
+ }
+ return bboxf;
+}
+
+static BoundingBoxf extrusionentity_extents(const ExtrusionEntity *extrusion_entity);
+
+static inline BoundingBoxf extrusionentity_extents(const ExtrusionEntityCollection &extrusion_entity_collection)
+{
+ BoundingBoxf bbox;
+ for (const ExtrusionEntity *extrusion_entity : extrusion_entity_collection.entities)
+ bbox.merge(extrusionentity_extents(extrusion_entity));
+ return bbox;
+}
+
+static BoundingBoxf extrusionentity_extents(const ExtrusionEntity *extrusion_entity)
+{
+ if (extrusion_entity == nullptr)
+ return BoundingBoxf();
+ auto *extrusion_path = dynamic_cast<const ExtrusionPath*>(extrusion_entity);
+ if (extrusion_path != nullptr)
+ return extrusionentity_extents(*extrusion_path);
+ auto *extrusion_loop = dynamic_cast<const ExtrusionLoop*>(extrusion_entity);
+ if (extrusion_loop != nullptr)
+ return extrusionentity_extents(*extrusion_loop);
+ auto *extrusion_multi_path = dynamic_cast<const ExtrusionMultiPath*>(extrusion_entity);
+ if (extrusion_multi_path != nullptr)
+ return extrusionentity_extents(*extrusion_multi_path);
+ auto *extrusion_entity_collection = dynamic_cast<const ExtrusionEntityCollection*>(extrusion_entity);
+ if (extrusion_entity_collection != nullptr)
+ return extrusionentity_extents(*extrusion_entity_collection);
+ throw std::runtime_error("Unexpected extrusion_entity type in extrusionentity_extents()");
+ return BoundingBoxf();
+}
+
+BoundingBoxf get_print_extrusions_extents(const Print &print)
+{
+ BoundingBoxf bbox(extrusionentity_extents(print.brim()));
+ bbox.merge(extrusionentity_extents(print.skirt()));
+ return bbox;
+}
+
+BoundingBoxf get_print_object_extrusions_extents(const PrintObject &print_object, const coordf_t max_print_z)
+{
+ BoundingBoxf bbox;
+ for (const Layer *layer : print_object.layers()) {
+ if (layer->print_z > max_print_z)
+ break;
+ BoundingBoxf bbox_this;
+ for (const LayerRegion *layerm : layer->regions()) {
+ bbox_this.merge(extrusionentity_extents(layerm->perimeters));
+ for (const ExtrusionEntity *ee : layerm->fills.entities)
+ // fill represents infill extrusions of a single island.
+ bbox_this.merge(extrusionentity_extents(*dynamic_cast<const ExtrusionEntityCollection*>(ee)));
+ }
+ const SupportLayer *support_layer = dynamic_cast<const SupportLayer*>(layer);
+ if (support_layer)
+ for (const ExtrusionEntity *extrusion_entity : support_layer->support_fills.entities)
+ bbox_this.merge(extrusionentity_extents(extrusion_entity));
+ for (const Point &offset : print_object.copies()) {
+ BoundingBoxf bbox_translated(bbox_this);
+ bbox_translated.translate(unscale(offset));
+ bbox.merge(bbox_translated);
+ }
+ }
+ return bbox;
+}
+
+// Returns a bounding box of a projection of the wipe tower for the layers <= max_print_z.
+// The projection does not contain the priming regions.
+BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_t max_print_z)
+{
+ // Wipe tower extrusions are saved as if the tower was at the origin with no rotation
+ // We need to get position and angle of the wipe tower to transform them to actual position.
+ Transform2d trafo =
+ Eigen::Translation2d(print.config().wipe_tower_x.value, print.config().wipe_tower_y.value) *
+ Eigen::Rotation2Dd(print.config().wipe_tower_rotation_angle.value);
+
+ BoundingBoxf bbox;
+ for (const std::vector<WipeTower::ToolChangeResult> &tool_changes : print.wipe_tower_data().tool_changes) {
+ if (! tool_changes.empty() && tool_changes.front().print_z > max_print_z)
+ break;
+ for (const WipeTower::ToolChangeResult &tcr : tool_changes) {
+ for (size_t i = 1; i < tcr.extrusions.size(); ++ i) {
+ const WipeTower::Extrusion &e = tcr.extrusions[i];
+ if (e.width > 0) {
+ Vec2d delta = 0.5 * Vec2d(e.width, e.width);
+ Vec2d p1 = trafo * Vec2d((&e - 1)->pos.x, (&e - 1)->pos.y);
+ Vec2d p2 = trafo * Vec2d(e.pos.x, e.pos.y);
+ bbox.merge(p1.cwiseMin(p2) - delta);
+ bbox.merge(p1.cwiseMax(p2) + delta);
+ }
+ }
+ }
+ }
+ return bbox;
+}
+
+// Returns a bounding box of the wipe tower priming extrusions.
+BoundingBoxf get_wipe_tower_priming_extrusions_extents(const Print &print)
+{
+ BoundingBoxf bbox;
+ if (print.wipe_tower_data().priming != nullptr) {
+ const WipeTower::ToolChangeResult &tcr = *print.wipe_tower_data().priming;
+ for (size_t i = 1; i < tcr.extrusions.size(); ++ i) {
+ const WipeTower::Extrusion &e = tcr.extrusions[i];
+ if (e.width > 0) {
+ Vec2d p1((&e - 1)->pos.x, (&e - 1)->pos.y);
+ Vec2d p2(e.pos.x, e.pos.y);
+ bbox.merge(p1);
+ coordf_t radius = 0.5 * e.width;
+ bbox.min(0) = std::min(bbox.min(0), std::min(p1(0), p2(0)) - radius);
+ bbox.min(1) = std::min(bbox.min(1), std::min(p1(1), p2(1)) - radius);
+ bbox.max(0) = std::max(bbox.max(0), std::max(p1(0), p2(0)) + radius);
+ bbox.max(1) = std::max(bbox.max(1), std::max(p1(1), p2(1)) + radius);
+ }
+ }
+ }
+ return bbox;
+}
+
+}
diff --git a/src/libslic3r/GCode/PrintExtents.hpp b/src/libslic3r/GCode/PrintExtents.hpp
new file mode 100644
index 000000000..db507689d
--- /dev/null
+++ b/src/libslic3r/GCode/PrintExtents.hpp
@@ -0,0 +1,30 @@
+// Measure extents of the planned extrusions.
+// To be used for collision reporting.
+
+#ifndef slic3r_PrintExtents_hpp_
+#define slic3r_PrintExtents_hpp_
+
+#include "libslic3r.h"
+
+namespace Slic3r {
+
+class Print;
+class PrintObject;
+class BoundingBoxf;
+
+// Returns a bounding box of a projection of the brim and skirt.
+BoundingBoxf get_print_extrusions_extents(const Print &print);
+
+// Returns a bounding box of a projection of the object extrusions at z <= max_print_z.
+BoundingBoxf get_print_object_extrusions_extents(const PrintObject &print_object, const coordf_t max_print_z);
+
+// Returns a bounding box of a projection of the wipe tower for the layers <= max_print_z.
+// The projection does not contain the priming regions.
+BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_t max_print_z);
+
+// Returns a bounding box of the wipe tower priming extrusions.
+BoundingBoxf get_wipe_tower_priming_extrusions_extents(const Print &print);
+
+};
+
+#endif /* slic3r_PrintExtents_hpp_ */
diff --git a/src/libslic3r/GCode/SpiralVase.cpp b/src/libslic3r/GCode/SpiralVase.cpp
new file mode 100644
index 000000000..8e8ae3075
--- /dev/null
+++ b/src/libslic3r/GCode/SpiralVase.cpp
@@ -0,0 +1,87 @@
+#include "SpiralVase.hpp"
+#include "GCode.hpp"
+#include <sstream>
+
+namespace Slic3r {
+
+std::string SpiralVase::process_layer(const std::string &gcode)
+{
+ /* This post-processor relies on several assumptions:
+ - all layers are processed through it, including those that are not supposed
+ to be transformed, in order to update the reader with the XY positions
+ - each call to this method includes a full layer, with a single Z move
+ at the beginning
+ - each layer is composed by suitable geometry (i.e. a single complete loop)
+ - loops were not clipped before calling this method */
+
+ // If we're not going to modify G-code, just feed it to the reader
+ // in order to update positions.
+ if (!this->enable) {
+ this->_reader.parse_buffer(gcode);
+ return gcode;
+ }
+
+ // Get total XY length for this layer by summing all extrusion moves.
+ float total_layer_length = 0;
+ float layer_height = 0;
+ float z;
+ bool set_z = false;
+
+ {
+ //FIXME Performance warning: This copies the GCodeConfig of the reader.
+ GCodeReader r = this->_reader; // clone
+ r.parse_buffer(gcode, [&total_layer_length, &layer_height, &z, &set_z]
+ (GCodeReader &reader, const GCodeReader::GCodeLine &line) {
+ if (line.cmd_is("G1")) {
+ if (line.extruding(reader)) {
+ total_layer_length += line.dist_XY(reader);
+ } else if (line.has(Z)) {
+ layer_height += line.dist_Z(reader);
+ if (!set_z) {
+ z = line.new_Z(reader);
+ set_z = true;
+ }
+ }
+ }
+ });
+ }
+
+ // Remove layer height from initial Z.
+ z -= layer_height;
+
+ std::string new_gcode;
+ this->_reader.parse_buffer(gcode, [&new_gcode, &z, &layer_height, &total_layer_length]
+ (GCodeReader &reader, GCodeReader::GCodeLine line) {
+ if (line.cmd_is("G1")) {
+ if (line.has_z()) {
+ // If this is the initial Z move of the layer, replace it with a
+ // (redundant) move to the last Z of previous layer.
+ line.set(reader, Z, z);
+ new_gcode += line.raw() + '\n';
+ return;
+ } else {
+ float dist_XY = line.dist_XY(reader);
+ if (dist_XY > 0) {
+ // horizontal move
+ if (line.extruding(reader)) {
+ z += dist_XY * layer_height / total_layer_length;
+ line.set(reader, Z, z);
+ new_gcode += line.raw() + '\n';
+ }
+ return;
+
+ /* Skip travel moves: the move to first perimeter point will
+ cause a visible seam when loops are not aligned in XY; by skipping
+ it we blend the first loop move in the XY plane (although the smoothness
+ of such blend depend on how long the first segment is; maybe we should
+ enforce some minimum length?). */
+ }
+ }
+ }
+ new_gcode += line.raw() + '\n';
+ });
+
+ return new_gcode;
+}
+
+}
diff --git a/src/libslic3r/GCode/SpiralVase.hpp b/src/libslic3r/GCode/SpiralVase.hpp
new file mode 100644
index 000000000..60aa668d8
--- /dev/null
+++ b/src/libslic3r/GCode/SpiralVase.hpp
@@ -0,0 +1,28 @@
+#ifndef slic3r_SpiralVase_hpp_
+#define slic3r_SpiralVase_hpp_
+
+#include "libslic3r.h"
+#include "GCodeReader.hpp"
+
+namespace Slic3r {
+
+class SpiralVase {
+ public:
+ bool enable;
+
+ SpiralVase(const PrintConfig &config)
+ : enable(false), _config(&config)
+ {
+ this->_reader.z() = this->_config->z_offset;
+ this->_reader.apply_config(*this->_config);
+ };
+ std::string process_layer(const std::string &gcode);
+
+ private:
+ const PrintConfig* _config;
+ GCodeReader _reader;
+};
+
+}
+
+#endif
diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp
new file mode 100644
index 000000000..175b69447
--- /dev/null
+++ b/src/libslic3r/GCode/ToolOrdering.cpp
@@ -0,0 +1,631 @@
+#include "Print.hpp"
+#include "ToolOrdering.hpp"
+
+// #define SLIC3R_DEBUG
+
+// Make assert active if SLIC3R_DEBUG
+#ifdef SLIC3R_DEBUG
+ #define DEBUG
+ #define _DEBUG
+ #undef NDEBUG
+#endif
+
+#include <cassert>
+#include <limits>
+
+namespace Slic3r {
+
+
+// Returns true in case that extruder a comes before b (b does not have to be present). False otherwise.
+bool LayerTools::is_extruder_order(unsigned int a, unsigned int b) const
+{
+ if (a==b)
+ return false;
+
+ for (auto extruder : extruders) {
+ if (extruder == a)
+ return true;
+ if (extruder == b)
+ return false;
+ }
+
+ return false;
+}
+
+
+// For the use case when each object is printed separately
+// (print.config().complete_objects is true).
+ToolOrdering::ToolOrdering(const PrintObject &object, unsigned int first_extruder, bool prime_multi_material)
+{
+ if (object.layers().empty())
+ return;
+
+ // Initialize the print layers for just a single object.
+ {
+ std::vector<coordf_t> zs;
+ zs.reserve(zs.size() + object.layers().size() + object.support_layers().size());
+ for (auto layer : object.layers())
+ zs.emplace_back(layer->print_z);
+ for (auto layer : object.support_layers())
+ zs.emplace_back(layer->print_z);
+ this->initialize_layers(zs);
+ }
+
+ // Collect extruders reuqired to print the layers.
+ this->collect_extruders(object);
+
+ // Reorder the extruders to minimize tool switches.
+ this->reorder_extruders(first_extruder);
+
+ this->fill_wipe_tower_partitions(object.print()->config(), object.layers().front()->print_z - object.layers().front()->height);
+
+ this->collect_extruder_statistics(prime_multi_material);
+}
+
+// For the use case when all objects are printed at once.
+// (print.config().complete_objects is false).
+ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool prime_multi_material)
+{
+ m_print_config_ptr = &print.config();
+
+ PrintObjectPtrs objects = print.get_printable_objects();
+ // Initialize the print layers for all objects and all layers.
+ coordf_t object_bottom_z = 0.;
+ {
+ std::vector<coordf_t> zs;
+ for (auto object : objects) {
+ zs.reserve(zs.size() + object->layers().size() + object->support_layers().size());
+ for (auto layer : object->layers())
+ zs.emplace_back(layer->print_z);
+ for (auto layer : object->support_layers())
+ zs.emplace_back(layer->print_z);
+ if (! object->layers().empty())
+ object_bottom_z = object->layers().front()->print_z - object->layers().front()->height;
+ }
+ this->initialize_layers(zs);
+ }
+
+ // Collect extruders reuqired to print the layers.
+ for (auto object : objects)
+ this->collect_extruders(*object);
+
+ // Reorder the extruders to minimize tool switches.
+ this->reorder_extruders(first_extruder);
+
+ this->fill_wipe_tower_partitions(print.config(), object_bottom_z);
+
+ this->collect_extruder_statistics(prime_multi_material);
+}
+
+
+LayerTools& ToolOrdering::tools_for_layer(coordf_t print_z)
+{
+ auto it_layer_tools = std::lower_bound(m_layer_tools.begin(), m_layer_tools.end(), LayerTools(print_z - EPSILON));
+ assert(it_layer_tools != m_layer_tools.end());
+ coordf_t dist_min = std::abs(it_layer_tools->print_z - print_z);
+ for (++ it_layer_tools; it_layer_tools != m_layer_tools.end(); ++it_layer_tools) {
+ coordf_t d = std::abs(it_layer_tools->print_z - print_z);
+ if (d >= dist_min)
+ break;
+ dist_min = d;
+ }
+ -- it_layer_tools;
+ assert(dist_min < EPSILON);
+ return *it_layer_tools;
+}
+
+void ToolOrdering::initialize_layers(std::vector<coordf_t> &zs)
+{
+ sort_remove_duplicates(zs);
+ // Merge numerically very close Z values.
+ for (size_t i = 0; i < zs.size();) {
+ // Find the last layer with roughly the same print_z.
+ size_t j = i + 1;
+ coordf_t zmax = zs[i] + EPSILON;
+ for (; j < zs.size() && zs[j] <= zmax; ++ j) ;
+ // Assign an average print_z to the set of layers with nearly equal print_z.
+ m_layer_tools.emplace_back(LayerTools(0.5 * (zs[i] + zs[j-1]), m_print_config_ptr));
+ i = j;
+ }
+}
+
+// Collect extruders reuqired to print layers.
+void ToolOrdering::collect_extruders(const PrintObject &object)
+{
+ // Collect the support extruders.
+ for (auto support_layer : object.support_layers()) {
+ LayerTools &layer_tools = this->tools_for_layer(support_layer->print_z);
+ ExtrusionRole role = support_layer->support_fills.role();
+ bool has_support = role == erMixed || role == erSupportMaterial;
+ bool has_interface = role == erMixed || role == erSupportMaterialInterface;
+ unsigned int extruder_support = object.config().support_material_extruder.value;
+ unsigned int extruder_interface = object.config().support_material_interface_extruder.value;
+ if (has_support)
+ layer_tools.extruders.push_back(extruder_support);
+ if (has_interface)
+ layer_tools.extruders.push_back(extruder_interface);
+ if (has_support || has_interface)
+ layer_tools.has_support = true;
+ }
+ // Collect the object extruders.
+ for (auto layer : object.layers()) {
+ LayerTools &layer_tools = this->tools_for_layer(layer->print_z);
+ // What extruders are required to print this object layer?
+ for (size_t region_id = 0; region_id < object.print()->regions().size(); ++ region_id) {
+ const LayerRegion *layerm = (region_id < layer->regions().size()) ? layer->regions()[region_id] : nullptr;
+ if (layerm == nullptr)
+ continue;
+ const PrintRegion &region = *object.print()->regions()[region_id];
+
+ if (! layerm->perimeters.entities.empty()) {
+ bool something_nonoverriddable = true;
+
+ if (m_print_config_ptr) { // in this case complete_objects is false (see ToolOrdering constructors)
+ something_nonoverriddable = false;
+ for (const auto& eec : layerm->perimeters.entities) // let's check if there are nonoverriddable entities
+ if (!layer_tools.wiping_extrusions().is_overriddable(dynamic_cast<const ExtrusionEntityCollection&>(*eec), *m_print_config_ptr, object, region)) {
+ something_nonoverriddable = true;
+ break;
+ }
+ }
+
+ if (something_nonoverriddable)
+ layer_tools.extruders.push_back(region.config().perimeter_extruder.value);
+
+ layer_tools.has_object = true;
+ }
+
+
+ bool has_infill = false;
+ bool has_solid_infill = false;
+ bool something_nonoverriddable = false;
+ for (const ExtrusionEntity *ee : layerm->fills.entities) {
+ // fill represents infill extrusions of a single island.
+ const auto *fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
+ ExtrusionRole role = fill->entities.empty() ? erNone : fill->entities.front()->role();
+ if (is_solid_infill(role))
+ has_solid_infill = true;
+ else if (role != erNone)
+ has_infill = true;
+
+ if (m_print_config_ptr) {
+ if (!something_nonoverriddable && !layer_tools.wiping_extrusions().is_overriddable(*fill, *m_print_config_ptr, object, region))
+ something_nonoverriddable = true;
+ }
+ }
+
+ if (something_nonoverriddable || !m_print_config_ptr)
+ {
+ if (has_solid_infill)
+ layer_tools.extruders.push_back(region.config().solid_infill_extruder);
+ if (has_infill)
+ layer_tools.extruders.push_back(region.config().infill_extruder);
+ }
+ if (has_solid_infill || has_infill)
+ layer_tools.has_object = true;
+ }
+ }
+
+ for (auto& layer : m_layer_tools) {
+ // Sort and remove duplicates
+ sort_remove_duplicates(layer.extruders);
+
+ // make sure that there are some tools for each object layer (e.g. tall wiping object will result in empty extruders vector)
+ if (layer.extruders.empty() && layer.has_object)
+ layer.extruders.push_back(0); // 0="dontcare" extruder - it will be taken care of in reorder_extruders
+ }
+}
+
+// Reorder extruders to minimize layer changes.
+void ToolOrdering::reorder_extruders(unsigned int last_extruder_id)
+{
+ if (m_layer_tools.empty())
+ return;
+
+ if (last_extruder_id == (unsigned int)-1) {
+ // The initial print extruder has not been decided yet.
+ // Initialize the last_extruder_id with the first non-zero extruder id used for the print.
+ last_extruder_id = 0;
+ for (size_t i = 0; i < m_layer_tools.size() && last_extruder_id == 0; ++ i) {
+ const LayerTools &lt = m_layer_tools[i];
+ for (unsigned int extruder_id : lt.extruders)
+ if (extruder_id > 0) {
+ last_extruder_id = extruder_id;
+ break;
+ }
+ }
+ if (last_extruder_id == 0)
+ // Nothing to extrude.
+ return;
+ } else
+ // 1 based index
+ ++ last_extruder_id;
+
+ for (LayerTools &lt : m_layer_tools) {
+ if (lt.extruders.empty())
+ continue;
+ if (lt.extruders.size() == 1 && lt.extruders.front() == 0)
+ lt.extruders.front() = last_extruder_id;
+ else {
+ if (lt.extruders.front() == 0)
+ // Pop the "don't care" extruder, the "don't care" region will be merged with the next one.
+ lt.extruders.erase(lt.extruders.begin());
+ // Reorder the extruders to start with the last one.
+ for (size_t i = 1; i < lt.extruders.size(); ++ i)
+ if (lt.extruders[i] == last_extruder_id) {
+ // Move the last extruder to the front.
+ memmove(lt.extruders.data() + 1, lt.extruders.data(), i * sizeof(unsigned int));
+ lt.extruders.front() = last_extruder_id;
+ break;
+ }
+ }
+ last_extruder_id = lt.extruders.back();
+ }
+
+ // Reindex the extruders, so they are zero based, not 1 based.
+ for (LayerTools &lt : m_layer_tools)
+ for (unsigned int &extruder_id : lt.extruders) {
+ assert(extruder_id > 0);
+ -- extruder_id;
+ }
+}
+
+
+
+void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z)
+{
+ if (m_layer_tools.empty())
+ return;
+
+ // Count the minimum number of tool changes per layer.
+ size_t last_extruder = size_t(-1);
+ for (LayerTools &lt : m_layer_tools) {
+ lt.wipe_tower_partitions = lt.extruders.size();
+ if (! lt.extruders.empty()) {
+ if (last_extruder == size_t(-1) || last_extruder == lt.extruders.front())
+ // The first extruder on this layer is equal to the current one, no need to do an initial tool change.
+ -- lt.wipe_tower_partitions;
+ last_extruder = lt.extruders.back();
+ }
+ }
+
+ // Propagate the wipe tower partitions down to support the upper partitions by the lower partitions.
+ for (int i = int(m_layer_tools.size()) - 2; i >= 0; -- i)
+ m_layer_tools[i].wipe_tower_partitions = std::max(m_layer_tools[i + 1].wipe_tower_partitions, m_layer_tools[i].wipe_tower_partitions);
+
+ //FIXME this is a hack to get the ball rolling.
+ for (LayerTools &lt : m_layer_tools)
+ lt.has_wipe_tower = (lt.has_object && lt.wipe_tower_partitions > 0) || lt.print_z < object_bottom_z + EPSILON;
+
+ // Test for a raft, insert additional wipe tower layer to fill in the raft separation gap.
+ double max_layer_height = std::numeric_limits<double>::max();
+ for (size_t i = 0; i < config.nozzle_diameter.values.size(); ++ i) {
+ double mlh = config.max_layer_height.values[i];
+ if (mlh == 0.)
+ mlh = 0.75 * config.nozzle_diameter.values[i];
+ max_layer_height = std::min(max_layer_height, mlh);
+ }
+ for (size_t i = 0; i + 1 < m_layer_tools.size(); ++ i) {
+ const LayerTools &lt = m_layer_tools[i];
+ const LayerTools &lt_next = m_layer_tools[i + 1];
+ if (lt.print_z < object_bottom_z + EPSILON && lt_next.print_z >= object_bottom_z + EPSILON) {
+ // lt is the last raft layer. Find the 1st object layer.
+ size_t j = i + 1;
+ for (; j < m_layer_tools.size() && ! m_layer_tools[j].has_wipe_tower; ++ j);
+ if (j < m_layer_tools.size()) {
+ const LayerTools &lt_object = m_layer_tools[j];
+ coordf_t gap = lt_object.print_z - lt.print_z;
+ assert(gap > 0.f);
+ if (gap > max_layer_height + EPSILON) {
+ // Insert one additional wipe tower layer between lh.print_z and lt_object.print_z.
+ LayerTools lt_new(0.5f * (lt.print_z + lt_object.print_z));
+ // Find the 1st layer above lt_new.
+ for (j = i + 1; j < m_layer_tools.size() && m_layer_tools[j].print_z < lt_new.print_z - EPSILON; ++ j);
+ if (std::abs(m_layer_tools[j].print_z - lt_new.print_z) < EPSILON) {
+ m_layer_tools[j].has_wipe_tower = true;
+ } else {
+ LayerTools &lt_extra = *m_layer_tools.insert(m_layer_tools.begin() + j, lt_new);
+ LayerTools &lt_prev = m_layer_tools[j - 1];
+ LayerTools &lt_next = m_layer_tools[j + 1];
+ assert(! lt_prev.extruders.empty() && ! lt_next.extruders.empty());
+ assert(lt_prev.extruders.back() == lt_next.extruders.front());
+ lt_extra.has_wipe_tower = true;
+ lt_extra.extruders.push_back(lt_next.extruders.front());
+ lt_extra.wipe_tower_partitions = lt_next.wipe_tower_partitions;
+ }
+ }
+ }
+ break;
+ }
+ }
+
+ // Calculate the wipe_tower_layer_height values.
+ coordf_t wipe_tower_print_z_last = 0.;
+ for (LayerTools &lt : m_layer_tools)
+ if (lt.has_wipe_tower) {
+ lt.wipe_tower_layer_height = lt.print_z - wipe_tower_print_z_last;
+ wipe_tower_print_z_last = lt.print_z;
+ }
+}
+
+void ToolOrdering::collect_extruder_statistics(bool prime_multi_material)
+{
+ m_first_printing_extruder = (unsigned int)-1;
+ for (const auto &lt : m_layer_tools)
+ if (! lt.extruders.empty()) {
+ m_first_printing_extruder = lt.extruders.front();
+ break;
+ }
+
+ m_last_printing_extruder = (unsigned int)-1;
+ for (auto lt_it = m_layer_tools.rbegin(); lt_it != m_layer_tools.rend(); ++ lt_it)
+ if (! lt_it->extruders.empty()) {
+ m_last_printing_extruder = lt_it->extruders.back();
+ break;
+ }
+
+ m_all_printing_extruders.clear();
+ for (const auto &lt : m_layer_tools) {
+ append(m_all_printing_extruders, lt.extruders);
+ sort_remove_duplicates(m_all_printing_extruders);
+ }
+
+ if (prime_multi_material && ! m_all_printing_extruders.empty()) {
+ // Reorder m_all_printing_extruders in the sequence they will be primed, the last one will be m_first_printing_extruder.
+ // Then set m_first_printing_extruder to the 1st extruder primed.
+ m_all_printing_extruders.erase(
+ std::remove_if(m_all_printing_extruders.begin(), m_all_printing_extruders.end(),
+ [ this ](const unsigned int eid) { return eid == m_first_printing_extruder; }),
+ m_all_printing_extruders.end());
+ m_all_printing_extruders.emplace_back(m_first_printing_extruder);
+ m_first_printing_extruder = m_all_printing_extruders.front();
+ }
+}
+
+
+
+// This function is called from Print::mark_wiping_extrusions and sets extruder this entity should be printed with (-1 .. as usual)
+void WipingExtrusions::set_extruder_override(const ExtrusionEntity* entity, unsigned int copy_id, int extruder, unsigned int num_of_copies)
+{
+ something_overridden = true;
+
+ auto entity_map_it = (entity_map.insert(std::make_pair(entity, std::vector<int>()))).first; // (add and) return iterator
+ auto& copies_vector = entity_map_it->second;
+ if (copies_vector.size() < num_of_copies)
+ copies_vector.resize(num_of_copies, -1);
+
+ if (copies_vector[copy_id] != -1)
+ std::cout << "ERROR: Entity extruder overriden multiple times!!!\n"; // A debugging message - this must never happen.
+
+ copies_vector[copy_id] = extruder;
+}
+
+
+// Finds first non-soluble extruder on the layer
+int WipingExtrusions::first_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const
+{
+ const LayerTools& lt = *m_layer_tools;
+ for (auto extruders_it = lt.extruders.begin(); extruders_it != lt.extruders.end(); ++extruders_it)
+ if (!print_config.filament_soluble.get_at(*extruders_it))
+ return (*extruders_it);
+
+ return (-1);
+}
+
+// Finds last non-soluble extruder on the layer
+int WipingExtrusions::last_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const
+{
+ const LayerTools& lt = *m_layer_tools;
+ for (auto extruders_it = lt.extruders.rbegin(); extruders_it != lt.extruders.rend(); ++extruders_it)
+ if (!print_config.filament_soluble.get_at(*extruders_it))
+ return (*extruders_it);
+
+ return (-1);
+}
+
+
+// Decides whether this entity could be overridden
+bool WipingExtrusions::is_overriddable(const ExtrusionEntityCollection& eec, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) const
+{
+ if (print_config.filament_soluble.get_at(Print::get_extruder(eec, region)))
+ return false;
+
+ if (object.config().wipe_into_objects)
+ return true;
+
+ if (!region.config().wipe_into_infill || eec.role() != erInternalInfill)
+ return false;
+
+ return true;
+}
+
+
+// Following function iterates through all extrusions on the layer, remembers those that could be used for wiping after toolchange
+// and returns volume that is left to be wiped on the wipe tower.
+float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int old_extruder, unsigned int new_extruder, float volume_to_wipe)
+{
+ const LayerTools& lt = *m_layer_tools;
+ const float min_infill_volume = 0.f; // ignore infill with smaller volume than this
+
+ if (print.config().filament_soluble.get_at(old_extruder) || print.config().filament_soluble.get_at(new_extruder))
+ return volume_to_wipe; // Soluble filament cannot be wiped in a random infill, neither the filament after it
+
+ // we will sort objects so that dedicated for wiping are at the beginning:
+ PrintObjectPtrs object_list = print.get_printable_objects();
+ std::sort(object_list.begin(), object_list.end(), [](const PrintObject* a, const PrintObject* b) { return a->config().wipe_into_objects; });
+
+ // We will now iterate through
+ // - first the dedicated objects to mark perimeters or infills (depending on infill_first)
+ // - second through the dedicated ones again to mark infills or perimeters (depending on infill_first)
+ // - then all the others to mark infills (in case that !infill_first, we must also check that the perimeter is finished already
+ // this is controlled by the following variable:
+ bool perimeters_done = false;
+
+ for (int i=0 ; i<(int)object_list.size() + (perimeters_done ? 0 : 1); ++i) {
+ if (!perimeters_done && (i==(int)object_list.size() || !object_list[i]->config().wipe_into_objects)) { // we passed the last dedicated object in list
+ perimeters_done = true;
+ i=-1; // let's go from the start again
+ continue;
+ }
+
+ const auto& object = object_list[i];
+
+ // Finds this layer:
+ auto this_layer_it = std::find_if(object->layers().begin(), object->layers().end(), [&lt](const Layer* lay) { return std::abs(lt.print_z - lay->print_z)<EPSILON; });
+ if (this_layer_it == object->layers().end())
+ continue;
+ const Layer* this_layer = *this_layer_it;
+ unsigned int num_of_copies = object->copies().size();
+
+ for (unsigned int copy = 0; copy < num_of_copies; ++copy) { // iterate through copies first, so that we mark neighbouring infills to minimize travel moves
+
+ for (size_t region_id = 0; region_id < object->print()->regions().size(); ++ region_id) {
+ const auto& region = *object->print()->regions()[region_id];
+
+ if (!region.config().wipe_into_infill && !object->config().wipe_into_objects)
+ continue;
+
+
+ if ((!print.config().infill_first ? perimeters_done : !perimeters_done) || (!object->config().wipe_into_objects && region.config().wipe_into_infill)) {
+ for (const ExtrusionEntity* ee : this_layer->regions()[region_id]->fills.entities) { // iterate through all infill Collections
+ auto* fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
+
+ if (!is_overriddable(*fill, print.config(), *object, region))
+ continue;
+
+ // What extruder would this normally be printed with?
+ unsigned int correct_extruder = Print::get_extruder(*fill, region);
+
+ if (volume_to_wipe<=0)
+ continue;
+
+ if (!object->config().wipe_into_objects && !print.config().infill_first && region.config().wipe_into_infill)
+ // In this case we must check that the original extruder is used on this layer before the one we are overridding
+ // (and the perimeters will be finished before the infill is printed):
+ if (!lt.is_extruder_order(region.config().perimeter_extruder - 1, new_extruder))
+ continue;
+
+ if ((!is_entity_overridden(fill, copy) && fill->total_volume() > min_infill_volume)) { // this infill will be used to wipe this extruder
+ set_extruder_override(fill, copy, new_extruder, num_of_copies);
+ volume_to_wipe -= fill->total_volume();
+ }
+ }
+ }
+
+ // Now the same for perimeters - see comments above for explanation:
+ if (object->config().wipe_into_objects && (print.config().infill_first ? perimeters_done : !perimeters_done))
+ {
+ for (const ExtrusionEntity* ee : this_layer->regions()[region_id]->perimeters.entities) {
+ auto* fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
+ if (!is_overriddable(*fill, print.config(), *object, region))
+ continue;
+
+ if (volume_to_wipe<=0)
+ continue;
+
+ if ((!is_entity_overridden(fill, copy) && fill->total_volume() > min_infill_volume)) {
+ set_extruder_override(fill, copy, new_extruder, num_of_copies);
+ volume_to_wipe -= fill->total_volume();
+ }
+ }
+ }
+ }
+ }
+ }
+ return std::max(0.f, volume_to_wipe);
+}
+
+
+
+// Called after all toolchanges on a layer were mark_infill_overridden. There might still be overridable entities,
+// that were not actually overridden. If they are part of a dedicated object, printing them with the extruder
+// they were initially assigned to might mean violating the perimeter-infill order. We will therefore go through
+// them again and make sure we override it.
+void WipingExtrusions::ensure_perimeters_infills_order(const Print& print)
+{
+ const LayerTools& lt = *m_layer_tools;
+ unsigned int first_nonsoluble_extruder = first_nonsoluble_extruder_on_layer(print.config());
+ unsigned int last_nonsoluble_extruder = last_nonsoluble_extruder_on_layer(print.config());
+
+ PrintObjectPtrs printable_objects = print.get_printable_objects();
+ for (const PrintObject* object : printable_objects) {
+ // Finds this layer:
+ auto this_layer_it = std::find_if(object->layers().begin(), object->layers().end(), [&lt](const Layer* lay) { return std::abs(lt.print_z - lay->print_z)<EPSILON; });
+ if (this_layer_it == object->layers().end())
+ continue;
+ const Layer* this_layer = *this_layer_it;
+ unsigned int num_of_copies = object->copies().size();
+
+ for (unsigned int copy = 0; copy < num_of_copies; ++copy) { // iterate through copies first, so that we mark neighbouring infills to minimize travel moves
+ for (size_t region_id = 0; region_id < object->print()->regions().size(); ++ region_id) {
+ const auto& region = *object->print()->regions()[region_id];
+
+ if (!region.config().wipe_into_infill && !object->config().wipe_into_objects)
+ continue;
+
+ for (const ExtrusionEntity* ee : this_layer->regions()[region_id]->fills.entities) { // iterate through all infill Collections
+ auto* fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
+
+ if (!is_overriddable(*fill, print.config(), *object, region)
+ || is_entity_overridden(fill, copy) )
+ continue;
+
+ // This infill could have been overridden but was not - unless we do something, it could be
+ // printed before its perimeter, or not be printed at all (in case its original extruder has
+ // not been added to LayerTools
+ // Either way, we will now force-override it with something suitable:
+ if (print.config().infill_first
+ || object->config().wipe_into_objects // in this case the perimeter is overridden, so we can override by the last one safely
+ || lt.is_extruder_order(region.config().perimeter_extruder - 1, last_nonsoluble_extruder // !infill_first, but perimeter is already printed when last extruder prints
+ || std::find(lt.extruders.begin(), lt.extruders.end(), region.config().infill_extruder - 1) == lt.extruders.end()) // we have to force override - this could violate infill_first (FIXME)
+ )
+ set_extruder_override(fill, copy, (print.config().infill_first ? first_nonsoluble_extruder : last_nonsoluble_extruder), num_of_copies);
+ else {
+ // In this case we can (and should) leave it to be printed normally.
+ // Force overriding would mean it gets printed before its perimeter.
+ }
+ }
+
+ // Now the same for perimeters - see comments above for explanation:
+ for (const ExtrusionEntity* ee : this_layer->regions()[region_id]->perimeters.entities) { // iterate through all perimeter Collections
+ auto* fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
+ if (!is_overriddable(*fill, print.config(), *object, region)
+ || is_entity_overridden(fill, copy) )
+ continue;
+
+ set_extruder_override(fill, copy, (print.config().infill_first ? last_nonsoluble_extruder : first_nonsoluble_extruder), num_of_copies);
+ }
+ }
+ }
+ }
+}
+
+
+
+
+
+
+
+// Following function is called from process_layer and returns pointer to vector with information about which extruders should be used for given copy of this entity.
+// It first makes sure the pointer is valid (creates the vector if it does not exist) and contains a record for each copy
+// It also modifies the vector in place and changes all -1 to correct_extruder_id (at the time the overrides were created, correct extruders were not known,
+// so -1 was used as "print as usual".
+// The resulting vector has to keep track of which extrusions are the ones that were overridden and which were not. In the extruder is used as overridden,
+// its number is saved as it is (zero-based index). Usual extrusions are saved as -number-1 (unfortunately there is no negative zero).
+const std::vector<int>* WipingExtrusions::get_extruder_overrides(const ExtrusionEntity* entity, int correct_extruder_id, int num_of_copies)
+{
+ auto entity_map_it = entity_map.find(entity);
+ if (entity_map_it == entity_map.end())
+ entity_map_it = (entity_map.insert(std::make_pair(entity, std::vector<int>()))).first;
+
+ // Now the entity_map_it should be valid, let's make sure the vector is long enough:
+ entity_map_it->second.resize(num_of_copies, -1);
+
+ // Each -1 now means "print as usual" - we will replace it with actual extruder id (shifted it so we don't lose that information):
+ std::replace(entity_map_it->second.begin(), entity_map_it->second.end(), -1, -correct_extruder_id-1);
+
+ return &(entity_map_it->second);
+}
+
+
+} // namespace Slic3r
diff --git a/src/libslic3r/GCode/ToolOrdering.hpp b/src/libslic3r/GCode/ToolOrdering.hpp
new file mode 100644
index 000000000..4dcf6516a
--- /dev/null
+++ b/src/libslic3r/GCode/ToolOrdering.hpp
@@ -0,0 +1,162 @@
+// Ordering of the tools to minimize tool switches.
+
+#ifndef slic3r_ToolOrdering_hpp_
+#define slic3r_ToolOrdering_hpp_
+
+#include "libslic3r.h"
+
+namespace Slic3r {
+
+class Print;
+class PrintObject;
+class LayerTools;
+
+
+
+// Object of this class holds information about whether an extrusion is printed immediately
+// after a toolchange (as part of infill/perimeter wiping) or not. One extrusion can be a part
+// of several copies - this has to be taken into account.
+class WipingExtrusions
+{
+public:
+ bool is_anything_overridden() const { // if there are no overrides, all the agenda can be skipped - this function can tell us if that's the case
+ return something_overridden;
+ }
+
+ // This is called from GCode::process_layer - see implementation for further comments:
+ const std::vector<int>* get_extruder_overrides(const ExtrusionEntity* entity, int correct_extruder_id, int num_of_copies);
+
+ // This function goes through all infill entities, decides which ones will be used for wiping and
+ // marks them by the extruder id. Returns volume that remains to be wiped on the wipe tower:
+ float mark_wiping_extrusions(const Print& print, unsigned int old_extruder, unsigned int new_extruder, float volume_to_wipe);
+
+ void ensure_perimeters_infills_order(const Print& print);
+
+ bool is_overriddable(const ExtrusionEntityCollection& ee, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) const;
+
+ void set_layer_tools_ptr(const LayerTools* lt) { m_layer_tools = lt; }
+
+private:
+ int first_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const;
+ int last_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const;
+
+ // This function is called from mark_wiping_extrusions and sets extruder that it should be printed with (-1 .. as usual)
+ void set_extruder_override(const ExtrusionEntity* entity, unsigned int copy_id, int extruder, unsigned int num_of_copies);
+
+ // Returns true in case that entity is not printed with its usual extruder for a given copy:
+ bool is_entity_overridden(const ExtrusionEntity* entity, int copy_id) const {
+ return (entity_map.find(entity) == entity_map.end() ? false : entity_map.at(entity).at(copy_id) != -1);
+ }
+
+ std::map<const ExtrusionEntity*, std::vector<int>> entity_map; // to keep track of who prints what
+ bool something_overridden = false;
+ const LayerTools* m_layer_tools; // so we know which LayerTools object this belongs to
+};
+
+
+
+class LayerTools
+{
+public:
+ LayerTools(const coordf_t z, const PrintConfig* print_config_ptr = nullptr) :
+ print_z(z),
+ has_object(false),
+ has_support(false),
+ has_wipe_tower(false),
+ wipe_tower_partitions(0),
+ wipe_tower_layer_height(0.) {}
+
+ // Changing these operators to epsilon version can make a problem in cases where support and object layers get close to each other.
+ // In case someone tries to do it, make sure you know what you're doing and test it properly (slice multiple objects at once with supports).
+ bool operator< (const LayerTools &rhs) const { return print_z < rhs.print_z; }
+ bool operator==(const LayerTools &rhs) const { return print_z == rhs.print_z; }
+
+ bool is_extruder_order(unsigned int a, unsigned int b) const;
+
+ coordf_t print_z;
+ bool has_object;
+ bool has_support;
+ // Zero based extruder IDs, ordered to minimize tool switches.
+ std::vector<unsigned int> extruders;
+ // Will there be anything extruded on this layer for the wipe tower?
+ // Due to the support layers possibly interleaving the object layers,
+ // wipe tower will be disabled for some support only layers.
+ bool has_wipe_tower;
+ // Number of wipe tower partitions to support the required number of tool switches
+ // and to support the wipe tower partitions above this one.
+ size_t wipe_tower_partitions;
+ coordf_t wipe_tower_layer_height;
+
+ WipingExtrusions& wiping_extrusions() {
+ m_wiping_extrusions.set_layer_tools_ptr(this);
+ return m_wiping_extrusions;
+ }
+
+private:
+ // This object holds list of extrusion that will be used for extruder wiping
+ WipingExtrusions m_wiping_extrusions;
+};
+
+
+
+class ToolOrdering
+{
+public:
+ ToolOrdering() {}
+
+ // For the use case when each object is printed separately
+ // (print.config.complete_objects is true).
+ ToolOrdering(const PrintObject &object, unsigned int first_extruder = (unsigned int)-1, bool prime_multi_material = false);
+
+ // For the use case when all objects are printed at once.
+ // (print.config.complete_objects is false).
+ ToolOrdering(const Print &print, unsigned int first_extruder = (unsigned int)-1, bool prime_multi_material = false);
+
+ void clear() { m_layer_tools.clear(); }
+
+ // Get the first extruder printing, including the extruder priming areas, returns -1 if there is no layer printed.
+ unsigned int first_extruder() const { return m_first_printing_extruder; }
+
+ // Get the first extruder printing the layer_tools, returns -1 if there is no layer printed.
+ unsigned int last_extruder() const { return m_last_printing_extruder; }
+
+ // For a multi-material print, the printing extruders are ordered in the order they shall be primed.
+ const std::vector<unsigned int>& all_extruders() const { return m_all_printing_extruders; }
+
+ // Find LayerTools with the closest print_z.
+ LayerTools& tools_for_layer(coordf_t print_z);
+ const LayerTools& tools_for_layer(coordf_t print_z) const
+ { return *const_cast<const LayerTools*>(&const_cast<const ToolOrdering*>(this)->tools_for_layer(print_z)); }
+
+ const LayerTools& front() const { return m_layer_tools.front(); }
+ const LayerTools& back() const { return m_layer_tools.back(); }
+ std::vector<LayerTools>::const_iterator begin() const { return m_layer_tools.begin(); }
+ std::vector<LayerTools>::const_iterator end() const { return m_layer_tools.end(); }
+ bool empty() const { return m_layer_tools.empty(); }
+ std::vector<LayerTools>& layer_tools() { return m_layer_tools; }
+ bool has_wipe_tower() const { return ! m_layer_tools.empty() && m_first_printing_extruder != (unsigned int)-1 && m_layer_tools.front().wipe_tower_partitions > 0; }
+
+private:
+ void initialize_layers(std::vector<coordf_t> &zs);
+ void collect_extruders(const PrintObject &object);
+ void reorder_extruders(unsigned int last_extruder_id);
+ void fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z);
+ void collect_extruder_statistics(bool prime_multi_material);
+
+ std::vector<LayerTools> m_layer_tools;
+ // First printing extruder, including the multi-material priming sequence.
+ unsigned int m_first_printing_extruder = (unsigned int)-1;
+ // Final printing extruder.
+ unsigned int m_last_printing_extruder = (unsigned int)-1;
+ // All extruders, which extrude some material over m_layer_tools.
+ std::vector<unsigned int> m_all_printing_extruders;
+
+
+ const PrintConfig* m_print_config_ptr = nullptr;
+};
+
+
+
+} // namespace SLic3r
+
+#endif /* slic3r_ToolOrdering_hpp_ */
diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp
new file mode 100644
index 000000000..5cbbc1ca9
--- /dev/null
+++ b/src/libslic3r/GCode/WipeTower.hpp
@@ -0,0 +1,167 @@
+#ifndef slic3r_WipeTower_hpp_
+#define slic3r_WipeTower_hpp_
+
+#include <utility>
+#include <string>
+#include <vector>
+
+namespace Slic3r
+{
+
+// A pure virtual WipeTower definition.
+class WipeTower
+{
+public:
+ // Internal point class, to make the wipe tower independent from other slic3r modules.
+ // This is important for Prusa Research as we want to build the wipe tower post-processor independently from slic3r.
+ struct xy
+ {
+ xy(float x = 0.f, float y = 0.f) : x(x), y(y) {}
+ xy(const xy& pos,float xp,float yp) : x(pos.x+xp), y(pos.y+yp) {}
+ xy operator+(const xy &rhs) const { xy out(*this); out.x += rhs.x; out.y += rhs.y; return out; }
+ xy operator-(const xy &rhs) const { xy out(*this); out.x -= rhs.x; out.y -= rhs.y; return out; }
+ xy& operator+=(const xy &rhs) { x += rhs.x; y += rhs.y; return *this; }
+ xy& operator-=(const xy &rhs) { x -= rhs.x; y -= rhs.y; return *this; }
+ bool operator==(const xy &rhs) const { return x == rhs.x && y == rhs.y; }
+ bool operator!=(const xy &rhs) const { return x != rhs.x || y != rhs.y; }
+
+ // Rotate the point around center of the wipe tower about given angle (in degrees)
+ xy rotate(float width, float depth, float angle) const {
+ xy out(0,0);
+ float temp_x = x - width / 2.f;
+ float temp_y = y - depth / 2.f;
+ angle *= float(M_PI/180.);
+ out.x += temp_x * cos(angle) - temp_y * sin(angle) + width / 2.f;
+ out.y += temp_x * sin(angle) + temp_y * cos(angle) + depth / 2.f;
+ return out;
+ }
+
+ // Rotate the point around origin about given angle in degrees
+ void rotate(float angle) {
+ float temp_x = x * cos(angle) - y * sin(angle);
+ y = x * sin(angle) + y * cos(angle);
+ x = temp_x;
+ }
+
+ void translate(const xy& vect) {
+ x += vect.x;
+ y += vect.y;
+ }
+
+ float x;
+ float y;
+ };
+
+ WipeTower() {}
+ virtual ~WipeTower() {}
+
+ // Return the wipe tower position.
+ virtual const xy& position() const = 0;
+
+ // Return the wipe tower width.
+ virtual float width() const = 0;
+
+ // The wipe tower is finished, there should be no more tool changes or wipe tower prints.
+ virtual bool finished() const = 0;
+
+ // Switch to a next layer.
+ virtual void set_layer(
+ // Print height of this layer.
+ float print_z,
+ // Layer height, used to calculate extrusion the rate.
+ float layer_height,
+ // Maximum number of tool changes on this layer or the layers below.
+ size_t max_tool_changes,
+ // Is this the first layer of the print? In that case print the brim first.
+ bool is_first_layer,
+ // Is this the last layer of the wipe tower?
+ bool is_last_layer) = 0;
+
+ enum Purpose {
+ PURPOSE_MOVE_TO_TOWER,
+ PURPOSE_EXTRUDE,
+ PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE,
+ };
+
+ // Extrusion path of the wipe tower, for 3D preview of the generated tool paths.
+ struct Extrusion
+ {
+ Extrusion(const xy &pos, float width, unsigned int tool) : pos(pos), width(width), tool(tool) {}
+ // End position of this extrusion.
+ xy pos;
+ // Width of a squished extrusion, corrected for the roundings of the squished extrusions.
+ // This is left zero if it is a travel move.
+ float width;
+ // Current extruder index.
+ unsigned int tool;
+ };
+
+ struct ToolChangeResult
+ {
+ // Print heigh of this tool change.
+ float print_z;
+ float layer_height;
+ // G-code section to be directly included into the output G-code.
+ std::string gcode;
+ // For path preview.
+ std::vector<Extrusion> extrusions;
+ // Initial position, at which the wipe tower starts its action.
+ // At this position the extruder is loaded and there is no Z-hop applied.
+ xy start_pos;
+ // Last point, at which the normal G-code generator of Slic3r shall continue.
+ // At this position the extruder is loaded and there is no Z-hop applied.
+ xy end_pos;
+ // Time elapsed over this tool change.
+ // This is useful not only for the print time estimation, but also for the control of layer cooling.
+ float elapsed_time;
+
+ // Is this a priming extrusion? (If so, the wipe tower rotation & translation will not be applied later)
+ bool priming;
+
+ // Sum the total length of the extrusion.
+ float total_extrusion_length_in_plane() {
+ float e_length = 0.f;
+ for (size_t i = 1; i < this->extrusions.size(); ++ i) {
+ const Extrusion &e = this->extrusions[i];
+ if (e.width > 0) {
+ xy v = e.pos - (&e - 1)->pos;
+ e_length += sqrt(v.x*v.x+v.y*v.y);
+ }
+ }
+ return e_length;
+ }
+ };
+
+ // Returns gcode to prime the nozzles at the front edge of the print bed.
+ virtual ToolChangeResult prime(
+ // print_z of the first layer.
+ float first_layer_height,
+ // Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object.
+ const std::vector<unsigned int> &tools,
+ // If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower.
+ // If false, the last priming are will be large enough to wipe the last extruder sufficiently.
+ bool last_wipe_inside_wipe_tower) = 0;
+
+ // Returns gcode for toolchange and the end position.
+ // if new_tool == -1, just unload the current filament over the wipe tower.
+ virtual ToolChangeResult tool_change(unsigned int new_tool, bool last_in_layer) = 0;
+
+ // Close the current wipe tower layer with a perimeter and possibly fill the unfilled space with a zig-zag.
+ // Call this method only if layer_finished() is false.
+ virtual ToolChangeResult finish_layer() = 0;
+
+ // Is the current layer finished? A layer is finished if either the wipe tower is finished, or
+ // the wipe tower has been completely covered by the tool change extrusions,
+ // or the rest of the tower has been filled by a sparse infill with the finish_layer() method.
+ virtual bool layer_finished() const = 0;
+
+ // Returns used filament length per extruder:
+ virtual std::vector<float> get_used_filament() const = 0;
+
+ // Returns total number of toolchanges:
+ virtual int get_number_of_toolchanges() const = 0;
+};
+
+}; // namespace Slic3r
+
+#endif /* slic3r_WipeTower_hpp_ */
diff --git a/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/src/libslic3r/GCode/WipeTowerPrusaMM.cpp
new file mode 100644
index 000000000..54bdfdfd6
--- /dev/null
+++ b/src/libslic3r/GCode/WipeTowerPrusaMM.cpp
@@ -0,0 +1,1258 @@
+/*
+
+TODO LIST
+---------
+
+1. cooling moves - DONE
+2. account for perimeter and finish_layer extrusions and subtract it from last wipe - DONE
+3. priming extrusions (last wipe must clear the color) - DONE
+4. Peter's wipe tower - layer's are not exactly square
+5. Peter's wipe tower - variable width for higher levels
+6. Peter's wipe tower - make sure it is not too sparse (apply max_bridge_distance and make last wipe longer)
+7. Peter's wipe tower - enable enhanced first layer adhesion
+
+*/
+
+#include "WipeTowerPrusaMM.hpp"
+
+#include <assert.h>
+#include <math.h>
+#include <iostream>
+#include <vector>
+#include <numeric>
+
+#include "Analyzer.hpp"
+
+#if defined(__linux) || defined(__GNUC__ )
+#include <strings.h>
+#endif /* __linux */
+
+#ifdef _MSC_VER
+#define strcasecmp _stricmp
+#endif
+
+
+namespace Slic3r
+{
+
+namespace PrusaMultiMaterial {
+
+class Writer
+{
+public:
+ Writer(float layer_height, float line_width) :
+ m_current_pos(std::numeric_limits<float>::max(), std::numeric_limits<float>::max()),
+ m_current_z(0.f),
+ m_current_feedrate(0.f),
+ m_layer_height(layer_height),
+ m_extrusion_flow(0.f),
+ m_preview_suppressed(false),
+ m_elapsed_time(0.f),
+ m_default_analyzer_line_width(line_width)
+ {
+ // adds tag for analyzer:
+ char buf[64];
+ sprintf(buf, ";%s%f\n", GCodeAnalyzer::Height_Tag.c_str(), m_layer_height); // don't rely on GCodeAnalyzer knowing the layer height - it knows nothing at priming
+ m_gcode += buf;
+ sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erWipeTower);
+ m_gcode += buf;
+ change_analyzer_line_width(line_width);
+ }
+
+ Writer& change_analyzer_line_width(float line_width) {
+ // adds tag for analyzer:
+ char buf[64];
+ sprintf(buf, ";%s%f\n", GCodeAnalyzer::Width_Tag.c_str(), line_width);
+ m_gcode += buf;
+ return *this;
+ }
+
+ Writer& set_initial_position(const WipeTower::xy &pos, float width = 0.f, float depth = 0.f, float internal_angle = 0.f) {
+ m_wipe_tower_width = width;
+ m_wipe_tower_depth = depth;
+ m_internal_angle = internal_angle;
+ m_start_pos = WipeTower::xy(pos,0.f,m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle);
+ m_current_pos = pos;
+ return *this;
+ }
+
+ Writer& set_initial_tool(const unsigned int tool) { m_current_tool = tool; return *this; }
+
+ Writer& set_z(float z)
+ { m_current_z = z; return *this; }
+
+ Writer& set_extrusion_flow(float flow)
+ { m_extrusion_flow = flow; return *this; }
+
+ Writer& set_y_shift(float shift) {
+ m_current_pos.y -= shift-m_y_shift;
+ m_y_shift = shift;
+ return (*this);
+ }
+
+ // Suppress / resume G-code preview in Slic3r. Slic3r will have difficulty to differentiate the various
+ // filament loading and cooling moves from normal extrusion moves. Therefore the writer
+ // is asked to suppres output of some lines, which look like extrusions.
+ Writer& suppress_preview() { change_analyzer_line_width(0.f); m_preview_suppressed = true; return *this; }
+ Writer& resume_preview() { change_analyzer_line_width(m_default_analyzer_line_width); m_preview_suppressed = false; return *this; }
+
+ Writer& feedrate(float f)
+ {
+ if (f != m_current_feedrate)
+ m_gcode += "G1" + set_format_F(f) + "\n";
+ return *this;
+ }
+
+ const std::string& gcode() const { return m_gcode; }
+ const std::vector<WipeTower::Extrusion>& extrusions() const { return m_extrusions; }
+ float x() const { return m_current_pos.x; }
+ float y() const { return m_current_pos.y; }
+ const WipeTower::xy& pos() const { return m_current_pos; }
+ const WipeTower::xy start_pos_rotated() const { return m_start_pos; }
+ const WipeTower::xy pos_rotated() const { return WipeTower::xy(m_current_pos, 0.f, m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle); }
+ float elapsed_time() const { return m_elapsed_time; }
+ float get_and_reset_used_filament_length() { float temp = m_used_filament_length; m_used_filament_length = 0.f; return temp; }
+
+ // Extrude with an explicitely provided amount of extrusion.
+ Writer& extrude_explicit(float x, float y, float e, float f = 0.f, bool record_length = false)
+ {
+ if (x == m_current_pos.x && y == m_current_pos.y && e == 0.f && (f == 0.f || f == m_current_feedrate))
+ // Neither extrusion nor a travel move.
+ return *this;
+
+ float dx = x - m_current_pos.x;
+ float dy = y - m_current_pos.y;
+ double len = sqrt(dx*dx+dy*dy);
+ if (record_length)
+ m_used_filament_length += e;
+
+
+ // Now do the "internal rotation" with respect to the wipe tower center
+ WipeTower::xy rotated_current_pos(WipeTower::xy(m_current_pos,0.f,m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle)); // this is where we are
+ WipeTower::xy rot(WipeTower::xy(x,y+m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle)); // this is where we want to go
+
+ if (! m_preview_suppressed && e > 0.f && len > 0.) {
+ // Width of a squished extrusion, corrected for the roundings of the squished extrusions.
+ // This is left zero if it is a travel move.
+ float width = float(double(e) * /*Filament_Area*/2.40528 / (len * m_layer_height));
+ // Correct for the roundings of a squished extrusion.
+ width += m_layer_height * float(1. - M_PI / 4.);
+ if (m_extrusions.empty() || m_extrusions.back().pos != rotated_current_pos)
+ m_extrusions.emplace_back(WipeTower::Extrusion(rotated_current_pos, 0, m_current_tool));
+ m_extrusions.emplace_back(WipeTower::Extrusion(WipeTower::xy(rot.x, rot.y), width, m_current_tool));
+ }
+
+ m_gcode += "G1";
+ if (std::abs(rot.x - rotated_current_pos.x) > EPSILON)
+ m_gcode += set_format_X(rot.x);
+
+ if (std::abs(rot.y - rotated_current_pos.y) > EPSILON)
+ m_gcode += set_format_Y(rot.y);
+
+
+ if (e != 0.f)
+ m_gcode += set_format_E(e);
+
+ if (f != 0.f && f != m_current_feedrate)
+ m_gcode += set_format_F(f);
+
+ m_current_pos.x = x;
+ m_current_pos.y = y;
+
+ // Update the elapsed time with a rough estimate.
+ m_elapsed_time += ((len == 0) ? std::abs(e) : len) / m_current_feedrate * 60.f;
+ m_gcode += "\n";
+ return *this;
+ }
+
+ Writer& extrude_explicit(const WipeTower::xy &dest, float e, float f = 0.f, bool record_length = false)
+ { return extrude_explicit(dest.x, dest.y, e, f, record_length); }
+
+ // Travel to a new XY position. f=0 means use the current value.
+ Writer& travel(float x, float y, float f = 0.f)
+ { return extrude_explicit(x, y, 0.f, f); }
+
+ Writer& travel(const WipeTower::xy &dest, float f = 0.f)
+ { return extrude_explicit(dest.x, dest.y, 0.f, f); }
+
+ // Extrude a line from current position to x, y with the extrusion amount given by m_extrusion_flow.
+ Writer& extrude(float x, float y, float f = 0.f)
+ {
+ float dx = x - m_current_pos.x;
+ float dy = y - m_current_pos.y;
+ return extrude_explicit(x, y, sqrt(dx*dx+dy*dy) * m_extrusion_flow, f, true);
+ }
+
+ Writer& extrude(const WipeTower::xy &dest, const float f = 0.f)
+ { return extrude(dest.x, dest.y, f); }
+
+ Writer& rectangle(const WipeTower::xy& ld,float width,float height,const float f = 0.f)
+ {
+ WipeTower::xy corners[4];
+ corners[0] = ld;
+ corners[1] = WipeTower::xy(ld,width,0.f);
+ corners[2] = WipeTower::xy(ld,width,height);
+ corners[3] = WipeTower::xy(ld,0.f,height);
+ int index_of_closest = 0;
+ if (x()-ld.x > ld.x+width-x()) // closer to the right
+ index_of_closest = 1;
+ if (y()-ld.y > ld.y+height-y()) // closer to the top
+ index_of_closest = (index_of_closest==0 ? 3 : 2);
+
+ travel(corners[index_of_closest].x, y()); // travel to the closest corner
+ travel(x(),corners[index_of_closest].y);
+
+ int i = index_of_closest;
+ do {
+ ++i;
+ if (i==4) i=0;
+ extrude(corners[i], f);
+ } while (i != index_of_closest);
+ return (*this);
+ }
+
+ Writer& load(float e, float f = 0.f)
+ {
+ if (e == 0.f && (f == 0.f || f == m_current_feedrate))
+ return *this;
+ m_gcode += "G1";
+ if (e != 0.f)
+ m_gcode += set_format_E(e);
+ if (f != 0.f && f != m_current_feedrate)
+ m_gcode += set_format_F(f);
+ m_gcode += "\n";
+ return *this;
+ }
+
+ // Derectract while moving in the X direction.
+ // If |x| > 0, the feed rate relates to the x distance,
+ // otherwise the feed rate relates to the e distance.
+ Writer& load_move_x(float x, float e, float f = 0.f)
+ { return extrude_explicit(x, m_current_pos.y, e, f); }
+
+ Writer& retract(float e, float f = 0.f)
+ { return load(-e, f); }
+
+// Loads filament while also moving towards given points in x-axis (x feedrate is limited by cutting the distance short if necessary)
+ Writer& load_move_x_advanced(float farthest_x, float loading_dist, float loading_speed, float max_x_speed = 50.f)
+ {
+ float time = std::abs(loading_dist / loading_speed);
+ float x_speed = std::min(max_x_speed, std::abs(farthest_x - x()) / time);
+ float feedrate = 60.f * std::hypot(x_speed, loading_speed);
+
+ float end_point = x() + (farthest_x > x() ? 1.f : -1.f) * x_speed * time;
+ return extrude_explicit(end_point, y(), loading_dist, feedrate);
+ }
+
+ // Elevate the extruder head above the current print_z position.
+ Writer& z_hop(float hop, float f = 0.f)
+ {
+ m_gcode += std::string("G1") + set_format_Z(m_current_z + hop);
+ if (f != 0 && f != m_current_feedrate)
+ m_gcode += set_format_F(f);
+ m_gcode += "\n";
+ return *this;
+ }
+
+ // Lower the extruder head back to the current print_z position.
+ Writer& z_hop_reset(float f = 0.f)
+ { return z_hop(0, f); }
+
+ // Move to x1, +y_increment,
+ // extrude quickly amount e to x2 with feed f.
+ Writer& ram(float x1, float x2, float dy, float e0, float e, float f)
+ {
+ extrude_explicit(x1, m_current_pos.y + dy, e0, f, true);
+ extrude_explicit(x2, m_current_pos.y, e, 0.f, true);
+ return *this;
+ }
+
+ // Let the end of the pulled out filament cool down in the cooling tube
+ // by moving up and down and moving the print head left / right
+ // at the current Y position to spread the leaking material.
+ Writer& cool(float x1, float x2, float e1, float e2, float f)
+ {
+ extrude_explicit(x1, m_current_pos.y, e1, f);
+ extrude_explicit(x2, m_current_pos.y, e2);
+ return *this;
+ }
+
+ Writer& set_tool(int tool)
+ {
+ char buf[64];
+ sprintf(buf, "T%d\n", tool);
+ m_gcode += buf;
+ m_current_tool = tool;
+ return *this;
+ }
+
+ // Set extruder temperature, don't wait by default.
+ Writer& set_extruder_temp(int temperature, bool wait = false)
+ {
+ char buf[128];
+ sprintf(buf, "M%d S%d\n", wait ? 109 : 104, temperature);
+ m_gcode += buf;
+ return *this;
+ };
+
+ // Wait for a period of time (seconds).
+ Writer& wait(float time)
+ {
+ if (time==0)
+ return *this;
+ char buf[128];
+ sprintf(buf, "G4 S%.3f\n", time);
+ m_gcode += buf;
+ return *this;
+ };
+
+ // Set speed factor override percentage.
+ Writer& speed_override(int speed)
+ {
+ char buf[128];
+ sprintf(buf, "M220 S%d\n", speed);
+ m_gcode += buf;
+ return *this;
+ };
+
+ // Set digital trimpot motor
+ Writer& set_extruder_trimpot(int current)
+ {
+ char buf[128];
+ sprintf(buf, "M907 E%d\n", current);
+ m_gcode += buf;
+ return *this;
+ };
+
+ Writer& flush_planner_queue()
+ {
+ m_gcode += "G4 S0\n";
+ return *this;
+ }
+
+ // Reset internal extruder counter.
+ Writer& reset_extruder()
+ {
+ m_gcode += "G92 E0\n";
+ return *this;
+ }
+
+ Writer& comment_with_value(const char *comment, int value)
+ {
+ char strvalue[64];
+ sprintf(strvalue, "%d", value);
+ m_gcode += std::string(";") + comment + strvalue + "\n";
+ return *this;
+ };
+
+
+ Writer& set_fan(unsigned int speed)
+ {
+ if (speed == m_last_fan_speed)
+ return *this;
+
+ if (speed == 0)
+ m_gcode += "M107\n";
+ else
+ {
+ m_gcode += "M106 S";
+ char buf[128];
+ sprintf(buf,"%u\n",(unsigned int)(255.0 * speed / 100.0));
+ m_gcode += buf;
+ }
+ m_last_fan_speed = speed;
+ return *this;
+ }
+
+ Writer& comment_material(WipeTowerPrusaMM::material_type material)
+ {
+ m_gcode += "; material : ";
+ switch (material)
+ {
+ case WipeTowerPrusaMM::PVA:
+ m_gcode += "#8 (PVA)";
+ break;
+ case WipeTowerPrusaMM::SCAFF:
+ m_gcode += "#5 (Scaffold)";
+ break;
+ case WipeTowerPrusaMM::FLEX:
+ m_gcode += "#4 (Flex)";
+ break;
+ default:
+ m_gcode += "DEFAULT (PLA)";
+ break;
+ }
+ m_gcode += "\n";
+ return *this;
+ };
+
+ Writer& append(const char *text) { m_gcode += text; return *this; }
+
+private:
+ WipeTower::xy m_start_pos;
+ WipeTower::xy m_current_pos;
+ float m_current_z;
+ float m_current_feedrate;
+ unsigned int m_current_tool;
+ float m_layer_height;
+ float m_extrusion_flow;
+ bool m_preview_suppressed;
+ std::string m_gcode;
+ std::vector<WipeTower::Extrusion> m_extrusions;
+ float m_elapsed_time;
+ float m_internal_angle = 0.f;
+ float m_y_shift = 0.f;
+ float m_wipe_tower_width = 0.f;
+ float m_wipe_tower_depth = 0.f;
+ float m_last_fan_speed = 0.f;
+ int current_temp = -1;
+ const float m_default_analyzer_line_width;
+ float m_used_filament_length = 0.f;
+
+ std::string set_format_X(float x)
+ {
+ char buf[64];
+ sprintf(buf, " X%.3f", x);
+ m_current_pos.x = x;
+ return buf;
+ }
+
+ std::string set_format_Y(float y) {
+ char buf[64];
+ sprintf(buf, " Y%.3f", y);
+ m_current_pos.y = y;
+ return buf;
+ }
+
+ std::string set_format_Z(float z) {
+ char buf[64];
+ sprintf(buf, " Z%.3f", z);
+ return buf;
+ }
+
+ std::string set_format_E(float e) {
+ char buf[64];
+ sprintf(buf, " E%.4f", e);
+ return buf;
+ }
+
+ std::string set_format_F(float f) {
+ char buf[64];
+ sprintf(buf, " F%d", int(floor(f + 0.5f)));
+ m_current_feedrate = f;
+ return buf;
+ }
+
+ Writer& operator=(const Writer &rhs);
+}; // class Writer
+
+}; // namespace PrusaMultiMaterial
+
+
+
+WipeTowerPrusaMM::material_type WipeTowerPrusaMM::parse_material(const char *name)
+{
+ if (strcasecmp(name, "PLA") == 0)
+ return PLA;
+ if (strcasecmp(name, "ABS") == 0)
+ return ABS;
+ if (strcasecmp(name, "PET") == 0)
+ return PET;
+ if (strcasecmp(name, "HIPS") == 0)
+ return HIPS;
+ if (strcasecmp(name, "FLEX") == 0)
+ return FLEX;
+ if (strcasecmp(name, "SCAFF") == 0)
+ return SCAFF;
+ if (strcasecmp(name, "EDGE") == 0)
+ return EDGE;
+ if (strcasecmp(name, "NGEN") == 0)
+ return NGEN;
+ if (strcasecmp(name, "PVA") == 0)
+ return PVA;
+ return INVALID;
+}
+
+
+// Returns gcode to prime the nozzles at the front edge of the print bed.
+WipeTower::ToolChangeResult WipeTowerPrusaMM::prime(
+ // print_z of the first layer.
+ float first_layer_height,
+ // Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object.
+ const std::vector<unsigned int> &tools,
+ // If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower.
+ // If false, the last priming are will be large enough to wipe the last extruder sufficiently.
+ bool last_wipe_inside_wipe_tower)
+{
+ this->set_layer(first_layer_height, first_layer_height, tools.size(), true, false);
+ this->m_current_tool = tools.front();
+
+ // The Prusa i3 MK2 has a working space of [0, -2.2] to [250, 210].
+ // Due to the XYZ calibration, this working space may shrink slightly from all directions,
+ // therefore the homing position is shifted inside the bed by 0.2 in the firmware to [0.2, -2.0].
+// box_coordinates cleaning_box(xy(0.5f, - 1.5f), m_wipe_tower_width, wipe_area);
+
+ const float prime_section_width = std::min(240.f / tools.size(), 60.f);
+ box_coordinates cleaning_box(xy(5.f, 0.01f + m_perimeter_width/2.f), prime_section_width, 100.f);
+
+ PrusaMultiMaterial::Writer writer(m_layer_height, m_perimeter_width);
+ writer.set_extrusion_flow(m_extrusion_flow)
+ .set_z(m_z_pos)
+ .set_initial_tool(m_current_tool)
+ .append(";--------------------\n"
+ "; CP PRIMING START\n")
+ .append(";--------------------\n")
+ .speed_override(100);
+
+ writer.set_initial_position(xy(0.f, 0.f)) // Always move to the starting position
+ .travel(cleaning_box.ld, 7200)
+ .set_extruder_trimpot(750); // Increase the extruder driver current to allow fast ramming.
+
+ for (size_t idx_tool = 0; idx_tool < tools.size(); ++ idx_tool) {
+ unsigned int tool = tools[idx_tool];
+ m_left_to_right = true;
+ toolchange_Change(writer, tool, m_filpar[tool].material); // Select the tool, set a speed override for soluble and flex materials.
+ toolchange_Load(writer, cleaning_box); // Prime the tool.
+ if (idx_tool + 1 == tools.size()) {
+ // Last tool should not be unloaded, but it should be wiped enough to become of a pure color.
+ toolchange_Wipe(writer, cleaning_box, wipe_volumes[tools[idx_tool-1]][tool]);
+ } else {
+ // Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool.
+ //writer.travel(writer.x(), writer.y() + m_perimeter_width, 7200);
+ toolchange_Wipe(writer, cleaning_box , 20.f);
+ box_coordinates box = cleaning_box;
+ box.translate(0.f, writer.y() - cleaning_box.ld.y + m_perimeter_width);
+ toolchange_Unload(writer, box , m_filpar[m_current_tool].material, m_filpar[tools[idx_tool + 1]].first_layer_temperature);
+ cleaning_box.translate(prime_section_width, 0.f);
+ writer.travel(cleaning_box.ld, 7200);
+ }
+ ++ m_num_tool_changes;
+ }
+
+ m_old_temperature = -1; // If the priming is turned off in config, the temperature changing commands will not actually appear
+ // in the output gcode - we should not remember emitting them (we will output them twice in the worst case)
+
+ // Reset the extruder current to a normal value.
+ writer.set_extruder_trimpot(550)
+ .feedrate(6000)
+ .flush_planner_queue()
+ .reset_extruder()
+ .append("; CP PRIMING END\n"
+ ";------------------\n"
+ "\n\n");
+
+ // so that tool_change() will know to extrude the wipe tower brim:
+ m_print_brim = true;
+
+ // Ask our writer about how much material was consumed:
+ m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
+
+ ToolChangeResult result;
+ result.priming = true;
+ result.print_z = this->m_z_pos;
+ result.layer_height = this->m_layer_height;
+ result.gcode = writer.gcode();
+ result.elapsed_time = writer.elapsed_time();
+ result.extrusions = writer.extrusions();
+ result.start_pos = writer.start_pos_rotated();
+ result.end_pos = writer.pos_rotated();
+ return result;
+}
+
+WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, bool last_in_layer)
+{
+ if ( m_print_brim )
+ return toolchange_Brim();
+
+ float wipe_area = 0.f;
+ bool last_change_in_layer = false;
+ float wipe_volume = 0.f;
+
+ // Finds this toolchange info
+ if (tool != (unsigned int)(-1))
+ {
+ for (const auto &b : m_layer_info->tool_changes)
+ if ( b.new_tool == tool ) {
+ wipe_volume = b.wipe_volume;
+ if (tool == m_layer_info->tool_changes.back().new_tool)
+ last_change_in_layer = true;
+ wipe_area = b.required_depth * m_layer_info->extra_spacing;
+ break;
+ }
+ }
+ else {
+ // Otherwise we are going to Unload only. And m_layer_info would be invalid.
+ }
+
+ box_coordinates cleaning_box(
+ xy(m_perimeter_width / 2.f, m_perimeter_width / 2.f),
+ m_wipe_tower_width - m_perimeter_width,
+ (tool != (unsigned int)(-1) ? /*m_layer_info->depth*/wipe_area+m_depth_traversed-0.5*m_perimeter_width
+ : m_wipe_tower_depth-m_perimeter_width));
+
+ PrusaMultiMaterial::Writer writer(m_layer_height, m_perimeter_width);
+ writer.set_extrusion_flow(m_extrusion_flow)
+ .set_z(m_z_pos)
+ .set_initial_tool(m_current_tool)
+ .set_y_shift(m_y_shift + (tool!=(unsigned int)(-1) && (m_current_shape == SHAPE_REVERSED && !m_peters_wipe_tower) ? m_layer_info->depth - m_layer_info->toolchanges_depth(): 0.f))
+ .append(";--------------------\n"
+ "; CP TOOLCHANGE START\n")
+ .comment_with_value(" toolchange #", m_num_tool_changes + 1) // the number is zero-based
+ .comment_material(m_filpar[m_current_tool].material)
+ .append(";--------------------\n")
+ .speed_override(100);
+
+ xy initial_position = cleaning_box.ld + WipeTower::xy(0.f,m_depth_traversed);
+ writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation);
+
+ // Increase the extruder driver current to allow fast ramming.
+ writer.set_extruder_trimpot(750);
+
+ // Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool.
+ if (tool != (unsigned int)-1){ // This is not the last change.
+ toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material,
+ m_is_first_layer ? m_filpar[tool].first_layer_temperature : m_filpar[tool].temperature);
+ toolchange_Change(writer, tool, m_filpar[tool].material); // Change the tool, set a speed override for soluble and flex materials.
+ toolchange_Load(writer, cleaning_box);
+ writer.travel(writer.x(),writer.y()-m_perimeter_width); // cooling and loading were done a bit down the road
+ toolchange_Wipe(writer, cleaning_box, wipe_volume); // Wipe the newly loaded filament until the end of the assigned wipe area.
+ ++ m_num_tool_changes;
+ } else
+ toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, m_filpar[m_current_tool].temperature);
+
+ m_depth_traversed += wipe_area;
+
+ if (last_change_in_layer) {// draw perimeter line
+ writer.set_y_shift(m_y_shift);
+ if (m_peters_wipe_tower)
+ writer.rectangle(WipeTower::xy(0.f, 0.f),m_layer_info->depth + 3*m_perimeter_width,m_wipe_tower_depth);
+ else {
+ writer.rectangle(WipeTower::xy(0.f, 0.f),m_wipe_tower_width, m_layer_info->depth + m_perimeter_width);
+ if (layer_finished()) { // no finish_layer will be called, we must wipe the nozzle
+ writer.travel(writer.x()> m_wipe_tower_width / 2.f ? 0.f : m_wipe_tower_width, writer.y());
+ }
+ }
+ }
+
+ writer.set_extruder_trimpot(550) // Reset the extruder current to a normal value.
+ .feedrate(6000)
+ .flush_planner_queue()
+ .reset_extruder()
+ .append("; CP TOOLCHANGE END\n"
+ ";------------------\n"
+ "\n\n");
+
+ // Ask our writer about how much material was consumed:
+ m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
+
+ ToolChangeResult result;
+ result.priming = false;
+ result.print_z = this->m_z_pos;
+ result.layer_height = this->m_layer_height;
+ result.gcode = writer.gcode();
+ result.elapsed_time = writer.elapsed_time();
+ result.extrusions = writer.extrusions();
+ result.start_pos = writer.start_pos_rotated();
+ result.end_pos = writer.pos_rotated();
+ return result;
+}
+
+WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, float y_offset)
+{
+ const box_coordinates wipeTower_box(
+ WipeTower::xy(0.f, 0.f),
+ m_wipe_tower_width,
+ m_wipe_tower_depth);
+
+ PrusaMultiMaterial::Writer writer(m_layer_height, m_perimeter_width);
+ writer.set_extrusion_flow(m_extrusion_flow * 1.1f)
+ .set_z(m_z_pos) // Let the writer know the current Z position as a base for Z-hop.
+ .set_initial_tool(m_current_tool)
+ .append(";-------------------------------------\n"
+ "; CP WIPE TOWER FIRST LAYER BRIM START\n");
+
+ xy initial_position = wipeTower_box.lu - xy(m_perimeter_width * 6.f, 0);
+ writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation);
+
+ writer.extrude_explicit(wipeTower_box.ld - xy(m_perimeter_width * 6.f, 0), // Prime the extruder left of the wipe tower.
+ 1.5f * m_extrusion_flow * (wipeTower_box.lu.y - wipeTower_box.ld.y), 2400);
+
+ // The tool is supposed to be active and primed at the time when the wipe tower brim is extruded.
+ // Extrude 4 rounds of a brim around the future wipe tower.
+ box_coordinates box(wipeTower_box);
+ box.expand(m_perimeter_width);
+ for (size_t i = 0; i < 4; ++ i) {
+ writer.travel (box.ld, 7000)
+ .extrude(box.lu, 2100).extrude(box.ru)
+ .extrude(box.rd ).extrude(box.ld);
+ box.expand(m_perimeter_width);
+ }
+
+ writer.travel(wipeTower_box.ld, 7000); // Move to the front left corner.
+ writer.travel(wipeTower_box.rd) // Always wipe the nozzle with a long wipe to reduce stringing when moving away from the wipe tower.
+ .travel(wipeTower_box.ld);
+ writer.append("; CP WIPE TOWER FIRST LAYER BRIM END\n"
+ ";-----------------------------------\n");
+
+ m_print_brim = false; // Mark the brim as extruded
+
+ // Ask our writer about how much material was consumed:
+ m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
+
+ ToolChangeResult result;
+ result.priming = false;
+ result.print_z = this->m_z_pos;
+ result.layer_height = this->m_layer_height;
+ result.gcode = writer.gcode();
+ result.elapsed_time = writer.elapsed_time();
+ result.extrusions = writer.extrusions();
+ result.start_pos = writer.start_pos_rotated();
+ result.end_pos = writer.pos_rotated();
+ return result;
+}
+
+
+
+// Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool.
+void WipeTowerPrusaMM::toolchange_Unload(
+ PrusaMultiMaterial::Writer &writer,
+ const box_coordinates &cleaning_box,
+ const material_type current_material,
+ const int new_temperature)
+{
+ float xl = cleaning_box.ld.x + 1.f * m_perimeter_width;
+ float xr = cleaning_box.rd.x - 1.f * m_perimeter_width;
+
+ const float line_width = m_perimeter_width * m_filpar[m_current_tool].ramming_line_width_multiplicator; // desired ramming line thickness
+ const float y_step = line_width * m_filpar[m_current_tool].ramming_step_multiplicator * m_extra_spacing; // spacing between lines in mm
+
+ writer.append("; CP TOOLCHANGE UNLOAD\n")
+ .change_analyzer_line_width(line_width);
+
+ unsigned i = 0; // iterates through ramming_speed
+ m_left_to_right = true; // current direction of ramming
+ float remaining = xr - xl ; // keeps track of distance to the next turnaround
+ float e_done = 0; // measures E move done from each segment
+
+ writer.travel(xl, cleaning_box.ld.y + m_depth_traversed + y_step/2.f ); // move to starting position
+
+ // if the ending point of the ram would end up in mid air, align it with the end of the wipe tower:
+ if (m_layer_info > m_plan.begin() && m_layer_info < m_plan.end() && (m_layer_info-1!=m_plan.begin() || !m_adhesion )) {
+
+ // this is y of the center of previous sparse infill border
+ float sparse_beginning_y = 0.f;
+ if (m_current_shape == SHAPE_REVERSED)
+ sparse_beginning_y += ((m_layer_info-1)->depth - (m_layer_info-1)->toolchanges_depth())
+ - ((m_layer_info)->depth-(m_layer_info)->toolchanges_depth()) ;
+ else
+ sparse_beginning_y += (m_layer_info-1)->toolchanges_depth() + m_perimeter_width;
+
+ //debugging:
+ /* float oldx = writer.x();
+ float oldy = writer.y();
+ writer.travel(xr,sparse_beginning_y);
+ writer.extrude(xr+5,writer.y());
+ writer.travel(oldx,oldy);*/
+
+ float sum_of_depths = 0.f;
+ for (const auto& tch : m_layer_info->tool_changes) { // let's find this toolchange
+ if (tch.old_tool == m_current_tool) {
+ sum_of_depths += tch.ramming_depth;
+ float ramming_end_y = sum_of_depths;
+ ramming_end_y -= (y_step/m_extra_spacing-m_perimeter_width) / 2.f; // center of final ramming line
+
+ // debugging:
+ /*float oldx = writer.x();
+ float oldy = writer.y();
+ writer.travel(xl,ramming_end_y);
+ writer.extrude(xl-15,writer.y());
+ writer.travel(oldx,oldy);*/
+
+ if ( (m_current_shape == SHAPE_REVERSED && ramming_end_y < sparse_beginning_y - 0.5f*m_perimeter_width ) ||
+ (m_current_shape == SHAPE_NORMAL && ramming_end_y > sparse_beginning_y + 0.5f*m_perimeter_width ) )
+ {
+ writer.extrude(xl + tch.first_wipe_line-1.f*m_perimeter_width,writer.y());
+ remaining -= tch.first_wipe_line-1.f*m_perimeter_width;
+ }
+ break;
+ }
+ sum_of_depths += tch.required_depth;
+ }
+ }
+
+ // now the ramming itself:
+ while (i < m_filpar[m_current_tool].ramming_speed.size())
+ {
+ const float x = volume_to_length(m_filpar[m_current_tool].ramming_speed[i] * 0.25f, line_width, m_layer_height);
+ const float e = m_filpar[m_current_tool].ramming_speed[i] * 0.25f / Filament_Area; // transform volume per sec to E move;
+ const float dist = std::min(x - e_done, remaining); // distance to travel for either the next 0.25s, or to the next turnaround
+ const float actual_time = dist/x * 0.25;
+ writer.ram(writer.x(), writer.x() + (m_left_to_right ? 1.f : -1.f) * dist, 0, 0, e * (dist / x), std::hypot(dist, e * (dist / x)) / (actual_time / 60.));
+ remaining -= dist;
+
+ if (remaining < WT_EPSILON) { // we reached a turning point
+ writer.travel(writer.x(), writer.y() + y_step, 7200);
+ m_left_to_right = !m_left_to_right;
+ remaining = xr - xl;
+ }
+ e_done += dist; // subtract what was actually done
+ if (e_done > x - WT_EPSILON) { // current segment finished
+ ++i;
+ e_done = 0;
+ }
+ }
+ WipeTower::xy end_of_ramming(writer.x(),writer.y());
+ writer.change_analyzer_line_width(m_perimeter_width); // so the next lines are not affected by ramming_line_width_multiplier
+
+ // Retraction:
+ float old_x = writer.x();
+ float turning_point = (!m_left_to_right ? xl : xr );
+ float total_retraction_distance = m_cooling_tube_retraction + m_cooling_tube_length/2.f - 15.f; // the 15mm is reserved for the first part after ramming
+ writer.suppress_preview()
+ .retract(15.f, m_filpar[m_current_tool].unloading_speed_start * 60.f) // feedrate 5000mm/min = 83mm/s
+ .retract(0.70f * total_retraction_distance, 1.0f * m_filpar[m_current_tool].unloading_speed * 60.f)
+ .retract(0.20f * total_retraction_distance, 0.5f * m_filpar[m_current_tool].unloading_speed * 60.f)
+ .retract(0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed * 60.f)
+
+ /*.load_move_x_advanced(turning_point, -15.f, 83.f, 50.f) // this is done at fixed speed
+ .load_move_x_advanced(old_x, -0.70f * total_retraction_distance, 1.0f * m_filpar[m_current_tool].unloading_speed)
+ .load_move_x_advanced(turning_point, -0.20f * total_retraction_distance, 0.5f * m_filpar[m_current_tool].unloading_speed)
+ .load_move_x_advanced(old_x, -0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed)
+ .travel(old_x, writer.y()) // in case previous move was shortened to limit feedrate*/
+ .resume_preview();
+ if (new_temperature != 0 && (new_temperature != m_old_temperature || m_is_first_layer) ) { // Set the extruder temperature, but don't wait.
+ // If the required temperature is the same as last time, don't emit the M104 again (if user adjusted the value, it would be reset)
+ // However, always change temperatures on the first layer (this is to avoid issues with priming lines turned off).
+ writer.set_extruder_temp(new_temperature, false);
+ m_old_temperature = new_temperature;
+ }
+
+ // Cooling:
+ const int& number_of_moves = m_filpar[m_current_tool].cooling_moves;
+ if (number_of_moves > 0) {
+ const float& initial_speed = m_filpar[m_current_tool].cooling_initial_speed;
+ const float& final_speed = m_filpar[m_current_tool].cooling_final_speed;
+
+ float speed_inc = (final_speed - initial_speed) / (2.f * number_of_moves - 1.f);
+
+ writer.suppress_preview()
+ .travel(writer.x(), writer.y() + y_step);
+ old_x = writer.x();
+ turning_point = xr-old_x > old_x-xl ? xr : xl;
+ for (int i=0; i<number_of_moves; ++i) {
+ float speed = initial_speed + speed_inc * 2*i;
+ writer.load_move_x_advanced(turning_point, m_cooling_tube_length, speed);
+ speed += speed_inc;
+ writer.load_move_x_advanced(old_x, -m_cooling_tube_length, speed);
+ }
+ }
+
+ // let's wait is necessary:
+ writer.wait(m_filpar[m_current_tool].delay);
+ // we should be at the beginning of the cooling tube again - let's move to parking position:
+ writer.retract(-m_cooling_tube_length/2.f+m_parking_pos_retraction-m_cooling_tube_retraction, 2000);
+
+ // this is to align ramming and future wiping extrusions, so the future y-steps can be uniform from the start:
+ // the perimeter_width will later be subtracted, it is there to not load while moving over just extruded material
+ writer.travel(end_of_ramming.x, end_of_ramming.y + (y_step/m_extra_spacing-m_perimeter_width) / 2.f + m_perimeter_width, 2400.f);
+
+ writer.resume_preview()
+ .flush_planner_queue();
+}
+
+// Change the tool, set a speed override for soluble and flex materials.
+void WipeTowerPrusaMM::toolchange_Change(
+ PrusaMultiMaterial::Writer &writer,
+ const unsigned int new_tool,
+ material_type new_material)
+{
+ // Ask the writer about how much of the old filament we consumed:
+ m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
+
+ // Speed override for the material. Go slow for flex and soluble materials.
+ int speed_override;
+ switch (new_material) {
+ case PVA: speed_override = (m_z_pos < 0.80f) ? 60 : 80; break;
+ case SCAFF: speed_override = 35; break;
+ case FLEX: speed_override = 35; break;
+ default: speed_override = 100;
+ }
+ writer.set_tool(new_tool)
+ .speed_override(speed_override)
+ .flush_planner_queue();
+ m_current_tool = new_tool;
+}
+
+
+
+void WipeTowerPrusaMM::toolchange_Load(
+ PrusaMultiMaterial::Writer &writer,
+ const box_coordinates &cleaning_box)
+{
+ float xl = cleaning_box.ld.x + m_perimeter_width * 0.75f;
+ float xr = cleaning_box.rd.x - m_perimeter_width * 0.75f;
+ float oldx = writer.x(); // the nozzle is in place to do the first wiping moves, we will remember the position
+
+ // Load the filament while moving left / right, so the excess material will not create a blob at a single position.
+ float turning_point = ( oldx-xl < xr-oldx ? xr : xl );
+ float edist = m_parking_pos_retraction+m_extra_loading_move;
+
+ writer.append("; CP TOOLCHANGE LOAD\n")
+ .suppress_preview()
+ /*.load_move_x_advanced(turning_point, 0.2f * edist, 0.3f * m_filpar[m_current_tool].loading_speed) // Acceleration
+ .load_move_x_advanced(oldx, 0.5f * edist, m_filpar[m_current_tool].loading_speed) // Fast phase
+ .load_move_x_advanced(turning_point, 0.2f * edist, 0.3f * m_filpar[m_current_tool].loading_speed) // Slowing down
+ .load_move_x_advanced(oldx, 0.1f * edist, 0.1f * m_filpar[m_current_tool].loading_speed) // Super slow*/
+
+ .load(0.2f * edist, 60.f * m_filpar[m_current_tool].loading_speed_start)
+ .load_move_x_advanced(turning_point, 0.7f * edist, m_filpar[m_current_tool].loading_speed) // Fast phase
+ .load_move_x_advanced(oldx, 0.1f * edist, 0.1f * m_filpar[m_current_tool].loading_speed) // Super slow*/
+
+ .travel(oldx, writer.y()) // in case last move was shortened to limit x feedrate
+ .resume_preview();
+
+ // Reset the extruder current to the normal value.
+ writer.set_extruder_trimpot(550);
+}
+
+
+
+
+// Wipe the newly loaded filament until the end of the assigned wipe area.
+void WipeTowerPrusaMM::toolchange_Wipe(
+ PrusaMultiMaterial::Writer &writer,
+ const box_coordinates &cleaning_box,
+ float wipe_volume)
+{
+ // Increase flow on first layer, slow down print.
+ writer.set_extrusion_flow(m_extrusion_flow * (m_is_first_layer ? 1.18f : 1.f))
+ .append("; CP TOOLCHANGE WIPE\n");
+ float wipe_coeff = m_is_first_layer ? 0.5f : 1.f;
+ const float& xl = cleaning_box.ld.x;
+ const float& xr = cleaning_box.rd.x;
+
+ // Variables x_to_wipe and traversed_x are here to be able to make sure it always wipes at least
+ // the ordered volume, even if it means violating the box. This can later be removed and simply
+ // wipe until the end of the assigned area.
+
+ float x_to_wipe = volume_to_length(wipe_volume, m_perimeter_width, m_layer_height);
+ float dy = m_extra_spacing*m_perimeter_width;
+ float wipe_speed = 1600.f;
+
+ // if there is less than 2.5*m_perimeter_width to the edge, advance straightaway (there is likely a blob anyway)
+ if ((m_left_to_right ? xr-writer.x() : writer.x()-xl) < 2.5f*m_perimeter_width) {
+ writer.travel((m_left_to_right ? xr-m_perimeter_width : xl+m_perimeter_width),writer.y()+dy);
+ m_left_to_right = !m_left_to_right;
+ }
+
+ // now the wiping itself:
+ for (int i = 0; true; ++i) {
+ if (i!=0) {
+ if (wipe_speed < 1610.f) wipe_speed = 1800.f;
+ else if (wipe_speed < 1810.f) wipe_speed = 2200.f;
+ else if (wipe_speed < 2210.f) wipe_speed = 4200.f;
+ else wipe_speed = std::min(4800.f, wipe_speed + 50.f);
+ }
+
+ float traversed_x = writer.x();
+ if (m_left_to_right)
+ writer.extrude(xr - (i % 4 == 0 ? 0 : 1.5*m_perimeter_width), writer.y(), wipe_speed * wipe_coeff);
+ else
+ writer.extrude(xl + (i % 4 == 1 ? 0 : 1.5*m_perimeter_width), writer.y(), wipe_speed * wipe_coeff);
+
+ if (writer.y()+EPSILON > cleaning_box.lu.y-0.5f*m_perimeter_width)
+ break; // in case next line would not fit
+
+ traversed_x -= writer.x();
+ x_to_wipe -= fabs(traversed_x);
+ if (x_to_wipe < WT_EPSILON) {
+ writer.travel(m_left_to_right ? xl + 1.5*m_perimeter_width : xr - 1.5*m_perimeter_width, writer.y(), 7200);
+ break;
+ }
+ // stepping to the next line:
+ writer.extrude(writer.x() + (i % 4 == 0 ? -1.f : (i % 4 == 1 ? 1.f : 0.f)) * 1.5*m_perimeter_width, writer.y() + dy);
+ m_left_to_right = !m_left_to_right;
+ }
+
+ // this is neither priming nor not the last toolchange on this layer - we are going back to the model - wipe the nozzle
+ if (m_layer_info != m_plan.end() && m_current_tool != m_layer_info->tool_changes.back().new_tool) {
+ m_left_to_right = !m_left_to_right;
+ writer.travel(writer.x(), writer.y() - dy)
+ .travel(m_left_to_right ? m_wipe_tower_width : 0.f, writer.y());
+ }
+
+ writer.set_extrusion_flow(m_extrusion_flow); // Reset the extrusion flow.
+}
+
+
+
+
+WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer()
+{
+ // This should only be called if the layer is not finished yet.
+ // Otherwise the caller would likely travel to the wipe tower in vain.
+ assert(! this->layer_finished());
+
+ PrusaMultiMaterial::Writer writer(m_layer_height, m_perimeter_width);
+ writer.set_extrusion_flow(m_extrusion_flow)
+ .set_z(m_z_pos)
+ .set_initial_tool(m_current_tool)
+ .set_y_shift(m_y_shift - (m_current_shape == SHAPE_REVERSED && !m_peters_wipe_tower ? m_layer_info->toolchanges_depth() : 0.f))
+ .append(";--------------------\n"
+ "; CP EMPTY GRID START\n")
+ .comment_with_value(" layer #", m_num_layer_changes + 1);
+
+ // Slow down on the 1st layer.
+ float speed_factor = m_is_first_layer ? 0.5f : 1.f;
+ float current_depth = m_layer_info->depth - m_layer_info->toolchanges_depth();
+ box_coordinates fill_box(xy(m_perimeter_width, m_depth_traversed + m_perimeter_width),
+ m_wipe_tower_width - 2 * m_perimeter_width, current_depth-m_perimeter_width);
+
+
+ writer.set_initial_position((m_left_to_right ? fill_box.ru : fill_box.lu), // so there is never a diagonal travel
+ m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation);
+
+ box_coordinates box = fill_box;
+ for (int i=0;i<2;++i) {
+ if (m_layer_info->toolchanges_depth() < WT_EPSILON) { // there were no toolchanges on this layer
+ if (i==0) box.expand(m_perimeter_width);
+ else box.expand(-m_perimeter_width);
+ }
+ else i=2; // only draw the inner perimeter, outer has been already drawn by tool_change(...)
+ writer.rectangle(box.ld,box.rd.x-box.ld.x,box.ru.y-box.rd.y,2900*speed_factor);
+ }
+
+ // we are in one of the corners, travel to ld along the perimeter:
+ if (writer.x() > fill_box.ld.x+EPSILON) writer.travel(fill_box.ld.x,writer.y());
+ if (writer.y() > fill_box.ld.y+EPSILON) writer.travel(writer.x(),fill_box.ld.y);
+
+ if (m_is_first_layer && m_adhesion) {
+ // Extrude a dense infill at the 1st layer to improve 1st layer adhesion of the wipe tower.
+ box.expand(-m_perimeter_width/2.f);
+ int nsteps = int(floor((box.lu.y - box.ld.y) / (2*m_perimeter_width)));
+ float step = (box.lu.y - box.ld.y) / nsteps;
+ writer.travel(box.ld-xy(m_perimeter_width/2.f,m_perimeter_width/2.f));
+ if (nsteps >= 0)
+ for (int i = 0; i < nsteps; ++i) {
+ writer.extrude(box.ld.x+m_perimeter_width/2.f, writer.y() + 0.5f * step);
+ writer.extrude(box.rd.x - m_perimeter_width / 2.f, writer.y());
+ writer.extrude(box.rd.x - m_perimeter_width / 2.f, writer.y() + 0.5f * step);
+ writer.extrude(box.ld.x + m_perimeter_width / 2.f, writer.y());
+ }
+ writer.travel(box.rd.x-m_perimeter_width/2.f,writer.y()); // wipe the nozzle
+ }
+ else { // Extrude a sparse infill to support the material to be printed above.
+ const float dy = (fill_box.lu.y - fill_box.ld.y - m_perimeter_width);
+ const float left = fill_box.lu.x+2*m_perimeter_width;
+ const float right = fill_box.ru.x - 2 * m_perimeter_width;
+ if (dy > m_perimeter_width)
+ {
+ // Extrude an inverse U at the left of the region.
+ writer.travel(fill_box.ld + xy(m_perimeter_width * 2, 0.f))
+ .extrude(fill_box.lu + xy(m_perimeter_width * 2, 0.f), 2900 * speed_factor);
+
+ const int n = 1+(right-left)/(m_bridging);
+ const float dx = (right-left)/n;
+ for (int i=1;i<=n;++i) {
+ float x=left+dx*i;
+ writer.travel(x,writer.y());
+ writer.extrude(x,i%2 ? fill_box.rd.y : fill_box.ru.y);
+ }
+ writer.travel(left,writer.y(),7200); // wipes the nozzle before moving away from the wipe tower
+ }
+ else
+ writer.travel(right,writer.y(),7200); // wipes the nozzle before moving away from the wipe tower
+ }
+ writer.append("; CP EMPTY GRID END\n"
+ ";------------------\n\n\n\n\n\n\n");
+
+ m_depth_traversed = m_wipe_tower_depth-m_perimeter_width;
+
+ // Ask our writer about how much material was consumed:
+ m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
+
+ ToolChangeResult result;
+ result.priming = false;
+ result.print_z = this->m_z_pos;
+ result.layer_height = this->m_layer_height;
+ result.gcode = writer.gcode();
+ result.elapsed_time = writer.elapsed_time();
+ result.extrusions = writer.extrusions();
+ result.start_pos = writer.start_pos_rotated();
+ result.end_pos = writer.pos_rotated();
+ return result;
+}
+
+// Appends a toolchange into m_plan and calculates neccessary depth of the corresponding box
+void WipeTowerPrusaMM::plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, bool brim, float wipe_volume)
+{
+ assert(m_plan.back().z <= z_par + WT_EPSILON ); // refuses to add a layer below the last one
+
+ if (m_plan.empty() || m_plan.back().z + WT_EPSILON < z_par) // if we moved to a new layer, we'll add it to m_plan first
+ m_plan.push_back(WipeTowerInfo(z_par, layer_height_par));
+
+ if (brim) { // this toolchange prints brim - we must add it to m_plan, but not to count its depth
+ m_plan.back().tool_changes.push_back(WipeTowerInfo::ToolChange(old_tool, new_tool));
+ return;
+ }
+
+ if (old_tool==new_tool) // new layer without toolchanges - we are done
+ return;
+
+ // this is an actual toolchange - let's calculate depth to reserve on the wipe tower
+ float depth = 0.f;
+ float width = m_wipe_tower_width - 3*m_perimeter_width;
+ float length_to_extrude = volume_to_length(0.25f * std::accumulate(m_filpar[old_tool].ramming_speed.begin(), m_filpar[old_tool].ramming_speed.end(), 0.f),
+ m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator,
+ layer_height_par);
+ depth = (int(length_to_extrude / width) + 1) * (m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator * m_filpar[old_tool].ramming_step_multiplicator);
+ float ramming_depth = depth;
+ length_to_extrude = width*((length_to_extrude / width)-int(length_to_extrude / width)) - width;
+ float first_wipe_line = -length_to_extrude;
+ length_to_extrude += volume_to_length(wipe_volume, m_perimeter_width, layer_height_par);
+ length_to_extrude = std::max(length_to_extrude,0.f);
+
+ depth += (int(length_to_extrude / width) + 1) * m_perimeter_width;
+ depth *= m_extra_spacing;
+
+ m_plan.back().tool_changes.push_back(WipeTowerInfo::ToolChange(old_tool, new_tool, depth, ramming_depth, first_wipe_line, wipe_volume));
+}
+
+
+
+void WipeTowerPrusaMM::plan_tower()
+{
+ // Calculate m_wipe_tower_depth (maximum depth for all the layers) and propagate depths downwards
+ m_wipe_tower_depth = 0.f;
+ for (auto& layer : m_plan)
+ layer.depth = 0.f;
+
+ for (int layer_index = m_plan.size() - 1; layer_index >= 0; --layer_index)
+ {
+ float this_layer_depth = std::max(m_plan[layer_index].depth, m_plan[layer_index].toolchanges_depth());
+ m_plan[layer_index].depth = this_layer_depth;
+
+ if (this_layer_depth > m_wipe_tower_depth - m_perimeter_width)
+ m_wipe_tower_depth = this_layer_depth + m_perimeter_width;
+
+ for (int i = layer_index - 1; i >= 0 ; i--)
+ {
+ if (m_plan[i].depth - this_layer_depth < 2*m_perimeter_width )
+ m_plan[i].depth = this_layer_depth;
+ }
+ }
+}
+
+void WipeTowerPrusaMM::save_on_last_wipe()
+{
+ for (m_layer_info=m_plan.begin();m_layer_info<m_plan.end();++m_layer_info) {
+ set_layer(m_layer_info->z, m_layer_info->height, 0, m_layer_info->z == m_plan.front().z, m_layer_info->z == m_plan.back().z);
+ if (m_layer_info->tool_changes.size()==0) // we have no way to save anything on an empty layer
+ continue;
+
+ for (const auto &toolchange : m_layer_info->tool_changes)
+ tool_change(toolchange.new_tool, false);
+
+ float width = m_wipe_tower_width - 3*m_perimeter_width; // width we draw into
+ float length_to_save = 2*(m_wipe_tower_width+m_wipe_tower_depth) + (!layer_finished() ? finish_layer().total_extrusion_length_in_plane() : 0.f);
+ float length_to_wipe = volume_to_length(m_layer_info->tool_changes.back().wipe_volume,
+ m_perimeter_width,m_layer_info->height) - m_layer_info->tool_changes.back().first_wipe_line - length_to_save;
+
+ length_to_wipe = std::max(length_to_wipe,0.f);
+ float depth_to_wipe = m_perimeter_width * (std::floor(length_to_wipe/width) + ( length_to_wipe > 0.f ? 1.f : 0.f ) ) * m_extra_spacing;
+
+ //depth += (int(length_to_extrude / width) + 1) * m_perimeter_width;
+ m_layer_info->tool_changes.back().required_depth = m_layer_info->tool_changes.back().ramming_depth + depth_to_wipe;
+ }
+}
+
+// Processes vector m_plan and calls respective functions to generate G-code for the wipe tower
+// Resulting ToolChangeResults are appended into vector "result"
+void WipeTowerPrusaMM::generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &result)
+{
+ if (m_plan.empty())
+
+ return;
+
+ m_extra_spacing = 1.f;
+
+ plan_tower();
+ for (int i=0;i<5;++i) {
+ save_on_last_wipe();
+ plan_tower();
+ }
+
+ if (m_peters_wipe_tower)
+ make_wipe_tower_square();
+
+ m_layer_info = m_plan.begin();
+ m_current_tool = (unsigned int)(-2); // we don't know which extruder to start with - we'll set it according to the first toolchange
+ for (auto& used : m_used_filament_length) // reset used filament stats
+ used = 0.f;
+
+ std::vector<WipeTower::ToolChangeResult> layer_result;
+ for (auto layer : m_plan)
+ {
+ set_layer(layer.z,layer.height,0,layer.z == m_plan.front().z,layer.z == m_plan.back().z);
+ if (m_peters_wipe_tower)
+ m_internal_rotation += 90.f;
+ else
+ m_internal_rotation += 180.f;
+
+ if (!m_peters_wipe_tower && m_layer_info->depth < m_wipe_tower_depth - m_perimeter_width)
+ m_y_shift = (m_wipe_tower_depth-m_layer_info->depth-m_perimeter_width)/2.f;
+
+ for (const auto &toolchange : layer.tool_changes) {
+ if (m_current_tool == (unsigned int)(-2))
+ m_current_tool = toolchange.old_tool;
+ layer_result.emplace_back(tool_change(toolchange.new_tool, false));
+ }
+
+ if (! layer_finished()) {
+ auto finish_layer_toolchange = finish_layer();
+ if ( ! layer.tool_changes.empty() ) { // we will merge it to the last toolchange
+ auto& last_toolchange = layer_result.back();
+ if (last_toolchange.end_pos != finish_layer_toolchange.start_pos) {
+ char buf[2048]; // Add a travel move from tc1.end_pos to tc2.start_pos.
+ sprintf(buf, "G1 X%.3f Y%.3f F7200\n", finish_layer_toolchange.start_pos.x, finish_layer_toolchange.start_pos.y);
+ last_toolchange.gcode += buf;
+ }
+ last_toolchange.gcode += finish_layer_toolchange.gcode;
+ last_toolchange.extrusions.insert(last_toolchange.extrusions.end(), finish_layer_toolchange.extrusions.begin(), finish_layer_toolchange.extrusions.end());
+ last_toolchange.end_pos = finish_layer_toolchange.end_pos;
+ }
+ else
+ layer_result.emplace_back(std::move(finish_layer_toolchange));
+ }
+
+ result.emplace_back(std::move(layer_result));
+ m_is_first_layer = false;
+ }
+}
+
+void WipeTowerPrusaMM::make_wipe_tower_square()
+{
+ const float width = m_wipe_tower_width - 3 * m_perimeter_width;
+ const float depth = m_wipe_tower_depth - m_perimeter_width;
+ // area that we actually print into is width*depth
+ float side = sqrt(depth * width);
+
+ m_wipe_tower_width = side + 3 * m_perimeter_width;
+ m_wipe_tower_depth = side + 2 * m_perimeter_width;
+ // For all layers, find how depth changed and update all toolchange depths
+ for (auto &lay : m_plan)
+ {
+ side = sqrt(lay.depth * width);
+ float width_ratio = width / side;
+
+ //lay.extra_spacing = width_ratio;
+ for (auto &tch : lay.tool_changes)
+ tch.required_depth *= width_ratio;
+ }
+
+ plan_tower(); // propagates depth downwards again (width has changed)
+ for (auto& lay : m_plan) // depths set, now the spacing
+ lay.extra_spacing = lay.depth / lay.toolchanges_depth();
+}
+
+}; // namespace Slic3r
diff --git a/src/libslic3r/GCode/WipeTowerPrusaMM.hpp b/src/libslic3r/GCode/WipeTowerPrusaMM.hpp
new file mode 100644
index 000000000..06625d189
--- /dev/null
+++ b/src/libslic3r/GCode/WipeTowerPrusaMM.hpp
@@ -0,0 +1,374 @@
+#ifndef WipeTowerPrusaMM_hpp_
+#define WipeTowerPrusaMM_hpp_
+
+#include <cmath>
+#include <string>
+#include <sstream>
+#include <utility>
+#include <algorithm>
+
+#include "WipeTower.hpp"
+
+
+namespace Slic3r
+{
+
+namespace PrusaMultiMaterial {
+ class Writer;
+};
+
+
+
+class WipeTowerPrusaMM : public WipeTower
+{
+public:
+ enum material_type
+ {
+ INVALID = -1,
+ PLA = 0, // E:210C B:55C
+ ABS = 1, // E:255C B:100C
+ PET = 2, // E:240C B:90C
+ HIPS = 3, // E:220C B:100C
+ FLEX = 4, // E:245C B:80C
+ SCAFF = 5, // E:215C B:55C
+ EDGE = 6, // E:240C B:80C
+ NGEN = 7, // E:230C B:80C
+ PVA = 8 // E:210C B:80C
+ };
+
+ // Parse material name into material_type.
+ static material_type parse_material(const char *name);
+
+ // x -- x coordinates of wipe tower in mm ( left bottom corner )
+ // y -- y coordinates of wipe tower in mm ( left bottom corner )
+ // width -- width of wipe tower in mm ( default 60 mm - leave as it is )
+ // wipe_area -- space available for one toolchange in mm
+ WipeTowerPrusaMM(float x, float y, float width, float rotation_angle, float cooling_tube_retraction,
+ float cooling_tube_length, float parking_pos_retraction, float extra_loading_move, float bridging,
+ const std::vector<std::vector<float>>& wiping_matrix, unsigned int initial_tool) :
+ m_wipe_tower_pos(x, y),
+ m_wipe_tower_width(width),
+ m_wipe_tower_rotation_angle(rotation_angle),
+ m_y_shift(0.f),
+ m_z_pos(0.f),
+ m_is_first_layer(false),
+ m_cooling_tube_retraction(cooling_tube_retraction),
+ m_cooling_tube_length(cooling_tube_length),
+ m_parking_pos_retraction(parking_pos_retraction),
+ m_extra_loading_move(extra_loading_move),
+ m_bridging(bridging),
+ m_current_tool(initial_tool),
+ wipe_volumes(wiping_matrix)
+ {}
+
+ virtual ~WipeTowerPrusaMM() {}
+
+
+ // Set the extruder properties.
+ void set_extruder(size_t idx, material_type material, int temp, int first_layer_temp, float loading_speed, float loading_speed_start,
+ float unloading_speed, float unloading_speed_start, float delay, int cooling_moves,
+ float cooling_initial_speed, float cooling_final_speed, std::string ramming_parameters, float nozzle_diameter)
+ {
+ //while (m_filpar.size() < idx+1) // makes sure the required element is in the vector
+ m_filpar.push_back(FilamentParameters());
+
+ m_filpar[idx].material = material;
+ m_filpar[idx].temperature = temp;
+ m_filpar[idx].first_layer_temperature = first_layer_temp;
+ m_filpar[idx].loading_speed = loading_speed;
+ m_filpar[idx].loading_speed_start = loading_speed_start;
+ m_filpar[idx].unloading_speed = unloading_speed;
+ m_filpar[idx].unloading_speed_start = unloading_speed_start;
+ m_filpar[idx].delay = delay;
+ m_filpar[idx].cooling_moves = cooling_moves;
+ m_filpar[idx].cooling_initial_speed = cooling_initial_speed;
+ m_filpar[idx].cooling_final_speed = cooling_final_speed;
+ m_filpar[idx].nozzle_diameter = nozzle_diameter; // to be used in future with (non-single) multiextruder MM
+
+ m_perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; // all extruders are now assumed to have the same diameter
+
+ std::stringstream stream{ramming_parameters};
+ float speed = 0.f;
+ stream >> m_filpar[idx].ramming_line_width_multiplicator >> m_filpar[idx].ramming_step_multiplicator;
+ m_filpar[idx].ramming_line_width_multiplicator /= 100;
+ m_filpar[idx].ramming_step_multiplicator /= 100;
+ while (stream >> speed)
+ m_filpar[idx].ramming_speed.push_back(speed);
+
+ m_used_filament_length.resize(std::max(m_used_filament_length.size(), idx + 1)); // makes sure that the vector is big enough so we don't have to check later
+ }
+
+
+ // Appends into internal structure m_plan containing info about the future wipe tower
+ // to be used before building begins. The entries must be added ordered in z.
+ void plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, bool brim, float wipe_volume = 0.f);
+
+ // Iterates through prepared m_plan, generates ToolChangeResults and appends them to "result"
+ void generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &result);
+
+ float get_depth() const { return m_wipe_tower_depth; }
+
+
+
+ // Switch to a next layer.
+ virtual void set_layer(
+ // Print height of this layer.
+ float print_z,
+ // Layer height, used to calculate extrusion the rate.
+ float layer_height,
+ // Maximum number of tool changes on this layer or the layers below.
+ size_t max_tool_changes,
+ // Is this the first layer of the print? In that case print the brim first.
+ bool is_first_layer,
+ // Is this the last layer of the waste tower?
+ bool is_last_layer)
+ {
+ m_z_pos = print_z;
+ m_layer_height = layer_height;
+ m_is_first_layer = is_first_layer;
+ m_print_brim = is_first_layer;
+ m_depth_traversed = 0.f;
+ m_current_shape = (! is_first_layer && m_current_shape == SHAPE_NORMAL) ? SHAPE_REVERSED : SHAPE_NORMAL;
+ if (is_first_layer) {
+ this->m_num_layer_changes = 0;
+ this->m_num_tool_changes = 0;
+ }
+ else
+ ++ m_num_layer_changes;
+
+ // Calculate extrusion flow from desired line width, nozzle diameter, filament diameter and layer_height:
+ m_extrusion_flow = extrusion_flow(layer_height);
+
+ // Advance m_layer_info iterator, making sure we got it right
+ while (!m_plan.empty() && m_layer_info->z < print_z - WT_EPSILON && m_layer_info+1 != m_plan.end())
+ ++m_layer_info;
+ }
+
+ // Return the wipe tower position.
+ virtual const xy& position() const { return m_wipe_tower_pos; }
+ // Return the wipe tower width.
+ virtual float width() const { return m_wipe_tower_width; }
+ // The wipe tower is finished, there should be no more tool changes or wipe tower prints.
+ virtual bool finished() const { return m_max_color_changes == 0; }
+
+ // Returns gcode to prime the nozzles at the front edge of the print bed.
+ virtual ToolChangeResult prime(
+ // print_z of the first layer.
+ float first_layer_height,
+ // Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object.
+ const std::vector<unsigned int> &tools,
+ // If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower.
+ // If false, the last priming are will be large enough to wipe the last extruder sufficiently.
+ bool last_wipe_inside_wipe_tower);
+
+ // Returns gcode for a toolchange and a final print head position.
+ // On the first layer, extrude a brim around the future wipe tower first.
+ virtual ToolChangeResult tool_change(unsigned int new_tool, bool last_in_layer);
+
+ // Fill the unfilled space with a sparse infill.
+ // Call this method only if layer_finished() is false.
+ virtual ToolChangeResult finish_layer();
+
+ // Is the current layer finished?
+ virtual bool layer_finished() const {
+ return ( (m_is_first_layer ? m_wipe_tower_depth - m_perimeter_width : m_layer_info->depth) - WT_EPSILON < m_depth_traversed);
+ }
+
+ virtual std::vector<float> get_used_filament() const override { return m_used_filament_length; }
+ virtual int get_number_of_toolchanges() const override { return m_num_tool_changes; }
+
+
+private:
+ WipeTowerPrusaMM();
+
+ enum wipe_shape // A fill-in direction
+ {
+ SHAPE_NORMAL = 1,
+ SHAPE_REVERSED = -1
+ };
+
+
+ const bool m_peters_wipe_tower = false; // sparse wipe tower inspired by Peter's post processor - not finished yet
+ const float Filament_Area = M_PI * 1.75f * 1.75f / 4.f; // filament area in mm^2
+ const float Width_To_Nozzle_Ratio = 1.25f; // desired line width (oval) in multiples of nozzle diameter - may not be actually neccessary to adjust
+ const float WT_EPSILON = 1e-3f;
+
+
+ xy m_wipe_tower_pos; // Left front corner of the wipe tower in mm.
+ float m_wipe_tower_width; // Width of the wipe tower.
+ float m_wipe_tower_depth = 0.f; // Depth of the wipe tower
+ float m_wipe_tower_rotation_angle = 0.f; // Wipe tower rotation angle in degrees (with respect to x axis)
+ float m_internal_rotation = 0.f;
+ float m_y_shift = 0.f; // y shift passed to writer
+ float m_z_pos = 0.f; // Current Z position.
+ float m_layer_height = 0.f; // Current layer height.
+ size_t m_max_color_changes = 0; // Maximum number of color changes per layer.
+ bool m_is_first_layer = false;// Is this the 1st layer of the print? If so, print the brim around the waste tower.
+ 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)
+
+ // G-code generator parameters.
+ float m_cooling_tube_retraction = 0.f;
+ float m_cooling_tube_length = 0.f;
+ float m_parking_pos_retraction = 0.f;
+ float m_extra_loading_move = 0.f;
+ float m_bridging = 0.f;
+ bool m_adhesion = true;
+
+ float m_perimeter_width = 0.4 * Width_To_Nozzle_Ratio; // Width of an extrusion line, also a perimeter spacing for 100% infill.
+ float m_extrusion_flow = 0.038; //0.029f;// Extrusion flow is derived from m_perimeter_width, layer height and filament diameter.
+
+
+ struct FilamentParameters {
+ material_type material = PLA;
+ int temperature = 0;
+ int first_layer_temperature = 0;
+ float loading_speed = 0.f;
+ float loading_speed_start = 0.f;
+ float unloading_speed = 0.f;
+ float unloading_speed_start = 0.f;
+ float delay = 0.f ;
+ int cooling_moves = 0;
+ float cooling_initial_speed = 0.f;
+ float cooling_final_speed = 0.f;
+ float ramming_line_width_multiplicator = 0.f;
+ float ramming_step_multiplicator = 0.f;
+ std::vector<float> ramming_speed;
+ float nozzle_diameter;
+ };
+
+ // Extruder specific parameters.
+ std::vector<FilamentParameters> m_filpar;
+
+
+ // State of the wipe tower generator.
+ unsigned int m_num_layer_changes = 0; // Layer change counter for the output statistics.
+ unsigned int m_num_tool_changes = 0; // Tool change change counter for the output statistics.
+ ///unsigned int m_idx_tool_change_in_layer = 0; // Layer change counter in this layer. Counting up to m_max_color_changes.
+ bool m_print_brim = true;
+ // A fill-in direction (positive Y, negative Y) alternates with each layer.
+ wipe_shape m_current_shape = SHAPE_NORMAL;
+ unsigned int m_current_tool = 0;
+ const std::vector<std::vector<float>> wipe_volumes;
+
+ float m_depth_traversed = 0.f; // Current y position at the wipe tower.
+ bool m_left_to_right = true;
+ float m_extra_spacing = 1.f;
+
+ // Calculates extrusion flow needed to produce required line width for given layer height
+ float extrusion_flow(float layer_height = -1.f) const // negative layer_height - return current m_extrusion_flow
+ {
+ if ( layer_height < 0 )
+ return m_extrusion_flow;
+ return layer_height * ( m_perimeter_width - layer_height * (1-M_PI/4.f)) / Filament_Area;
+ }
+
+ // Calculates length of extrusion line to extrude given volume
+ float volume_to_length(float volume, float line_width, float layer_height) const {
+ return std::max(0., volume / (layer_height * (line_width - layer_height * (1. - M_PI / 4.))));
+ }
+
+ // Calculates depth for all layers and propagates them downwards
+ void plan_tower();
+
+ // Goes through m_plan and recalculates depths and width of the WT to make it exactly square - experimental
+ void make_wipe_tower_square();
+
+ // Goes through m_plan, calculates border and finish_layer extrusions and subtracts them from last wipe
+ void save_on_last_wipe();
+
+
+ struct box_coordinates
+ {
+ box_coordinates(float left, float bottom, float width, float height) :
+ ld(left , bottom ),
+ lu(left , bottom + height),
+ rd(left + width, bottom ),
+ ru(left + width, bottom + height) {}
+ box_coordinates(const xy &pos, float width, float height) : box_coordinates(pos.x, pos.y, width, height) {}
+ void translate(const xy &shift) {
+ ld += shift; lu += shift;
+ rd += shift; ru += shift;
+ }
+ void translate(const float dx, const float dy) { translate(xy(dx, dy)); }
+ void expand(const float offset) {
+ ld += xy(- offset, - offset);
+ lu += xy(- offset, offset);
+ rd += xy( offset, - offset);
+ ru += xy( offset, offset);
+ }
+ void expand(const float offset_x, const float offset_y) {
+ ld += xy(- offset_x, - offset_y);
+ lu += xy(- offset_x, offset_y);
+ rd += xy( offset_x, - offset_y);
+ ru += xy( offset_x, offset_y);
+ }
+ xy ld; // left down
+ xy lu; // left upper
+ xy rd; // right lower
+ xy ru; // right upper
+ };
+
+
+ // to store information about tool changes for a given layer
+ struct WipeTowerInfo{
+ struct ToolChange {
+ unsigned int old_tool;
+ unsigned int new_tool;
+ float required_depth;
+ float ramming_depth;
+ float first_wipe_line;
+ float wipe_volume;
+ ToolChange(unsigned int old, unsigned int newtool, float depth=0.f, float ramming_depth=0.f, float fwl=0.f, float wv=0.f)
+ : old_tool{old}, new_tool{newtool}, required_depth{depth}, ramming_depth{ramming_depth}, first_wipe_line{fwl}, wipe_volume{wv} {}
+ };
+ float z; // z position of the layer
+ float height; // layer height
+ float depth; // depth of the layer based on all layers above
+ float extra_spacing;
+ float toolchanges_depth() const { float sum = 0.f; for (const auto &a : tool_changes) sum += a.required_depth; return sum; }
+
+ std::vector<ToolChange> tool_changes;
+
+ WipeTowerInfo(float z_par, float layer_height_par)
+ : z{z_par}, height{layer_height_par}, depth{0}, extra_spacing{1.f} {}
+ };
+
+ std::vector<WipeTowerInfo> m_plan; // Stores information about all layers and toolchanges for the future wipe tower (filled by plan_toolchange(...))
+ std::vector<WipeTowerInfo>::iterator m_layer_info = m_plan.end();
+
+ // Stores information about used filament length per extruder:
+ std::vector<float> m_used_filament_length;
+
+
+ // Returns gcode for wipe tower brim
+ // sideOnly -- set to false -- experimental, draw brim on sides of wipe tower
+ // offset -- set to 0 -- experimental, offset to replace brim in front / rear of wipe tower
+ ToolChangeResult toolchange_Brim(bool sideOnly = false, float y_offset = 0.f);
+
+ void toolchange_Unload(
+ PrusaMultiMaterial::Writer &writer,
+ const box_coordinates &cleaning_box,
+ const material_type current_material,
+ const int new_temperature);
+
+ void toolchange_Change(
+ PrusaMultiMaterial::Writer &writer,
+ const unsigned int new_tool,
+ material_type new_material);
+
+ void toolchange_Load(
+ PrusaMultiMaterial::Writer &writer,
+ const box_coordinates &cleaning_box);
+
+ void toolchange_Wipe(
+ PrusaMultiMaterial::Writer &writer,
+ const box_coordinates &cleaning_box,
+ float wipe_volume);
+};
+
+
+
+
+}; // namespace Slic3r
+
+#endif /* WipeTowerPrusaMM_hpp_ */