diff --git a/resources/icons/PrusaSlicerGCodeViewer_128px.png b/resources/icons/PrusaSlicerGCodeViewer_128px.png
new file mode 100644
index 0000000000..0bf85abbd6
Binary files /dev/null and b/resources/icons/PrusaSlicerGCodeViewer_128px.png differ
diff --git a/resources/icons/thumb_left.svg b/resources/icons/thumb_left.svg
new file mode 100644
index 0000000000..ef78bd1410
--- /dev/null
+++ b/resources/icons/thumb_left.svg
@@ -0,0 +1,54 @@
+
+
diff --git a/resources/icons/thumb_right.svg b/resources/icons/thumb_right.svg
new file mode 100644
index 0000000000..f3748525d2
--- /dev/null
+++ b/resources/icons/thumb_right.svg
@@ -0,0 +1,54 @@
+
+
diff --git a/resources/shaders/gouraud_light.fs b/resources/shaders/gouraud_light.fs
new file mode 100644
index 0000000000..1a58abc852
--- /dev/null
+++ b/resources/shaders/gouraud_light.fs
@@ -0,0 +1,11 @@
+#version 110
+
+uniform vec4 uniform_color;
+
+// x = tainted, y = specular;
+varying vec2 intensity;
+
+void main()
+{
+ gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color.rgb * intensity.x, uniform_color.a);
+}
diff --git a/resources/shaders/gouraud_light.vs b/resources/shaders/gouraud_light.vs
new file mode 100644
index 0000000000..d4f71938a9
--- /dev/null
+++ b/resources/shaders/gouraud_light.vs
@@ -0,0 +1,38 @@
+#version 110
+
+#define INTENSITY_CORRECTION 0.6
+
+// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31)
+const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
+#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION)
+#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION)
+#define LIGHT_TOP_SHININESS 20.0
+
+// normalized values for (1./1.43, 0.2/1.43, 1./1.43)
+const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
+#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION)
+
+#define INTENSITY_AMBIENT 0.3
+
+// x = tainted, y = specular;
+varying vec2 intensity;
+
+void main()
+{
+ // First transform the normal into camera space and normalize the result.
+ vec3 normal = normalize(gl_NormalMatrix * gl_Normal);
+
+ // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
+ // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
+ float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0);
+
+ intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
+ vec3 position = (gl_ModelViewMatrix * gl_Vertex).xyz;
+ intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS);
+
+ // Perform the same lighting calculation for the 2nd light source (no specular applied).
+ NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0);
+ intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
+
+ gl_Position = ftransform();
+}
diff --git a/resources/shaders/options_110.fs b/resources/shaders/options_110.fs
new file mode 100644
index 0000000000..ab656998df
--- /dev/null
+++ b/resources/shaders/options_110.fs
@@ -0,0 +1,8 @@
+#version 110
+
+uniform vec4 uniform_color;
+
+void main()
+{
+ gl_FragColor = uniform_color;
+}
diff --git a/resources/shaders/options_110.vs b/resources/shaders/options_110.vs
new file mode 100644
index 0000000000..7592f86a42
--- /dev/null
+++ b/resources/shaders/options_110.vs
@@ -0,0 +1,11 @@
+#version 110
+
+uniform float zoom;
+uniform float point_size;
+uniform float near_plane_height;
+
+void main()
+{
+ gl_Position = ftransform();
+ gl_PointSize = (gl_Position.w == 1.0) ? zoom * near_plane_height * point_size : near_plane_height * point_size / gl_Position.w;
+}
diff --git a/resources/shaders/options_120.fs b/resources/shaders/options_120.fs
new file mode 100644
index 0000000000..e9b61304f2
--- /dev/null
+++ b/resources/shaders/options_120.fs
@@ -0,0 +1,22 @@
+// version 120 is needed for gl_PointCoord
+#version 120
+
+uniform vec4 uniform_color;
+uniform float percent_outline_radius;
+uniform float percent_center_radius;
+
+vec4 calc_color(float radius, vec4 color)
+{
+ return ((radius < percent_center_radius) || (radius > 1.0 - percent_outline_radius)) ?
+ vec4(0.5 * color.rgb, color.a) : color;
+}
+
+void main()
+{
+ vec2 pos = (gl_PointCoord - 0.5) * 2.0;
+ float radius = length(pos);
+ if (radius > 1.0)
+ discard;
+
+ gl_FragColor = calc_color(radius, uniform_color);
+}
diff --git a/resources/shaders/options_120.vs b/resources/shaders/options_120.vs
new file mode 100644
index 0000000000..baf3cd3a7f
--- /dev/null
+++ b/resources/shaders/options_120.vs
@@ -0,0 +1,11 @@
+#version 120
+
+uniform float zoom;
+uniform float point_size;
+uniform float near_plane_height;
+
+void main()
+{
+ gl_Position = ftransform();
+ gl_PointSize = (gl_Position.w == 1.0) ? zoom * near_plane_height * point_size : near_plane_height * point_size / gl_Position.w;
+}
diff --git a/resources/shaders/toolpaths_lines.fs b/resources/shaders/toolpaths_lines.fs
new file mode 100644
index 0000000000..31151cdc17
--- /dev/null
+++ b/resources/shaders/toolpaths_lines.fs
@@ -0,0 +1,28 @@
+#version 110
+
+// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31)
+const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
+const vec3 LIGHT_FRONT_DIR = vec3(0.0, 0.0, 1.0);
+
+// x = ambient, y = top diffuse, z = front diffuse, w = global
+uniform vec4 light_intensity;
+uniform vec4 uniform_color;
+
+varying vec3 eye_normal;
+
+void main()
+{
+ vec3 normal = normalize(eye_normal);
+
+ // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
+ // Since these two are normalized the cosine is the dot product. Take the abs value to light the lines no matter in which direction the normal points.
+ float NdotL = abs(dot(normal, LIGHT_TOP_DIR));
+
+ float intensity = light_intensity.x + NdotL * light_intensity.y;
+
+ // Perform the same lighting calculation for the 2nd light source.
+ NdotL = abs(dot(normal, LIGHT_FRONT_DIR));
+ intensity += NdotL * light_intensity.z;
+
+ gl_FragColor = vec4(uniform_color.rgb * light_intensity.w * intensity, uniform_color.a);
+}
diff --git a/resources/shaders/toolpaths_lines.vs b/resources/shaders/toolpaths_lines.vs
new file mode 100644
index 0000000000..85d5c641f3
--- /dev/null
+++ b/resources/shaders/toolpaths_lines.vs
@@ -0,0 +1,19 @@
+#version 110
+
+varying vec3 eye_normal;
+
+vec3 world_normal()
+{
+ // the world normal is always parallel to the world XY plane
+ // the x component is stored into gl_Vertex.w
+ float x = gl_Vertex.w;
+ float y = sqrt(1.0 - x * x);
+ return vec3(x, y, 0.0);
+}
+
+void main()
+{
+ vec4 world_position = vec4(gl_Vertex.xyz, 1.0);
+ gl_Position = gl_ModelViewProjectionMatrix * world_position;
+ eye_normal = gl_NormalMatrix * world_normal();
+}
diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp
index c37afb805d..aaf3db9158 100644
--- a/src/PrusaSlicer.cpp
+++ b/src/PrusaSlicer.cpp
@@ -469,7 +469,11 @@ int CLI::run(int argc, char **argv)
print->process();
if (printer_technology == ptFFF) {
// The outfile is processed by a PlaceholderParser.
+#if ENABLE_GCODE_VIEWER
+ outfile = fff_print.export_gcode(outfile, nullptr, nullptr);
+#else
outfile = fff_print.export_gcode(outfile, nullptr);
+#endif // ENABLE_GCODE_VIEWER
outfile_final = fff_print.print_statistics().finalize_output_path(outfile);
} else {
outfile = sla_print.output_filepath(outfile);
diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt
index aea3247220..3d241dd371 100644
--- a/src/libslic3r/CMakeLists.txt
+++ b/src/libslic3r/CMakeLists.txt
@@ -99,6 +99,8 @@ add_library(libslic3r STATIC
GCode/ToolOrdering.hpp
GCode/WipeTower.cpp
GCode/WipeTower.hpp
+ GCode/GCodeProcessor.cpp
+ GCode/GCodeProcessor.hpp
GCode.cpp
GCode.hpp
GCodeReader.cpp
diff --git a/src/libslic3r/CustomGCode.cpp b/src/libslic3r/CustomGCode.cpp
index ba1890a1ff..fb4f69d06a 100644
--- a/src/libslic3r/CustomGCode.cpp
+++ b/src/libslic3r/CustomGCode.cpp
@@ -1,6 +1,10 @@
#include "CustomGCode.hpp"
#include "Config.hpp"
+#if ENABLE_GCODE_VIEWER
+#include "GCode.hpp"
+#else
#include "GCode/PreviewData.hpp"
+#endif // ENABLE_GCODE_VIEWER
#include "GCodeWriter.hpp"
namespace Slic3r {
@@ -17,8 +21,12 @@ extern void update_custom_gcode_per_print_z_from_config(Info& info, DynamicPrint
return;
if (info.gcodes.empty() && ! colorprint_heights->values.empty()) {
// Convert the old colorprint_heighs only if there is no equivalent data in a new format.
- const std::vector& colors = GCodePreviewData::ColorPrintColors();
- const auto& colorprint_values = colorprint_heights->values;
+#if ENABLE_GCODE_VIEWER
+ const std::vector& colors = ColorPrintColors::get();
+#else
+ const std::vector& colors = GCodePreviewData::ColorPrintColors();
+#endif // ENABLE_GCODE_VIEWER
+ const auto& colorprint_values = colorprint_heights->values;
info.gcodes.clear();
info.gcodes.reserve(colorprint_values.size());
int i = 0;
diff --git a/src/libslic3r/ExtrusionEntity.cpp b/src/libslic3r/ExtrusionEntity.cpp
index 69b3a6455d..b2c5e1350f 100644
--- a/src/libslic3r/ExtrusionEntity.cpp
+++ b/src/libslic3r/ExtrusionEntity.cpp
@@ -306,7 +306,11 @@ double ExtrusionLoop::min_mm3_per_mm() const
std::string ExtrusionEntity::role_to_string(ExtrusionRole role)
{
switch (role) {
+#if ENABLE_GCODE_VIEWER
+ case erNone : return L("Unknown");
+#else
case erNone : return L("None");
+#endif // ENABLE_GCODE_VIEWER
case erPerimeter : return L("Perimeter");
case erExternalPerimeter : return L("External perimeter");
case erOverhangPerimeter : return L("Overhang perimeter");
@@ -327,4 +331,40 @@ std::string ExtrusionEntity::role_to_string(ExtrusionRole role)
return "";
}
+ExtrusionRole ExtrusionEntity::string_to_role(const std::string& role)
+{
+ if (role == L("Perimeter"))
+ return erPerimeter;
+ else if (role == L("External perimeter"))
+ return erExternalPerimeter;
+ else if (role == L("Overhang perimeter"))
+ return erOverhangPerimeter;
+ else if (role == L("Internal infill"))
+ return erInternalInfill;
+ else if (role == L("Solid infill"))
+ return erSolidInfill;
+ else if (role == L("Top solid infill"))
+ return erTopSolidInfill;
+ else if (role == L("Ironing"))
+ return erIroning;
+ else if (role == L("Bridge infill"))
+ return erBridgeInfill;
+ else if (role == L("Gap fill"))
+ return erGapFill;
+ else if (role == L("Skirt"))
+ return erSkirt;
+ else if (role == L("Support material"))
+ return erSupportMaterial;
+ else if (role == L("Support material interface"))
+ return erSupportMaterialInterface;
+ else if (role == L("Wipe tower"))
+ return erWipeTower;
+ else if (role == L("Custom"))
+ return erCustom;
+ else if (role == L("Mixed"))
+ return erMixed;
+ else
+ return erNone;
+}
+
}
diff --git a/src/libslic3r/ExtrusionEntity.hpp b/src/libslic3r/ExtrusionEntity.hpp
index 879f564b6c..3d7e581128 100644
--- a/src/libslic3r/ExtrusionEntity.hpp
+++ b/src/libslic3r/ExtrusionEntity.hpp
@@ -106,6 +106,7 @@ public:
virtual double total_volume() const = 0;
static std::string role_to_string(ExtrusionRole role);
+ static ExtrusionRole string_to_role(const std::string& role);
};
typedef std::vector ExtrusionEntitiesPtr;
diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp
index 7d80677184..9e1600cb43 100644
--- a/src/libslic3r/GCode.cpp
+++ b/src/libslic3r/GCode.cpp
@@ -48,594 +48,599 @@ using namespace std::literals::string_view_literals;
namespace Slic3r {
-//! macro used to mark string used at localization,
-//! return same string
+ //! macro used to mark string used at localization,
+ //! return same string
#define L(s) (s)
#define _(s) Slic3r::I18N::translate(s)
// Only add a newline in case the current G-code does not end with a newline.
-static inline void check_add_eol(std::string &gcode)
-{
- if (! gcode.empty() && gcode.back() != '\n')
- gcode += '\n';
-}
-
-
-// Return true if tch_prefix is found in custom_gcode
-static bool custom_gcode_changes_tool(const std::string& custom_gcode, const std::string& tch_prefix, unsigned next_extruder)
-{
- bool ok = false;
- size_t from_pos = 0;
- size_t pos = 0;
- while ((pos = custom_gcode.find(tch_prefix, from_pos)) != std::string::npos) {
- if (pos+1 == custom_gcode.size())
- break;
- from_pos = pos+1;
- // only whitespace is allowed before the command
- while (--pos < custom_gcode.size() && custom_gcode[pos] != '\n') {
- if (! std::isspace(custom_gcode[pos]))
- goto NEXT;
- }
- {
- // we should also check that the extruder changes to what was expected
- std::istringstream ss(custom_gcode.substr(from_pos, std::string::npos));
- unsigned num = 0;
- if (ss >> num)
- ok = (num == next_extruder);
- }
-NEXT: ;
+ static inline void check_add_eol(std::string& gcode)
+ {
+ if (!gcode.empty() && gcode.back() != '\n')
+ gcode += '\n';
}
- return ok;
-}
-void AvoidCrossingPerimeters::init_external_mp(const Print &print)
-{
- m_external_mp = Slic3r::make_unique(union_ex(this->collect_contours_all_layers(print.objects())));
-}
-// Plan a travel move while minimizing the number of perimeter crossings.
-// point is in unscaled coordinates, in the coordinate system of the current active object
-// (set by gcodegen.set_origin()).
-Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &point)
-{
- // If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset).
- // Otherwise perform the path planning in the coordinate system of the active object.
- bool use_external = this->use_external_mp || this->use_external_mp_once;
- Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0);
- Polyline result = (use_external ? m_external_mp.get() : m_layer_mp.get())->
- shortest_path(gcodegen.last_pos() + scaled_origin, point + scaled_origin);
- if (use_external)
- result.translate(- scaled_origin);
- return result;
-}
-
-// Collect outer contours of all objects over all layers.
-// Discard objects only containing thin walls (offset would fail on an empty polygon).
-// Used by avoid crossing perimeters feature.
-Polygons AvoidCrossingPerimeters::collect_contours_all_layers(const PrintObjectPtrs& objects)
-{
- Polygons islands;
- for (const PrintObject *object : objects) {
- // Reducing all the object slices into the Z projection in a logarithimc fashion.
- // First reduce to half the number of layers.
- std::vector polygons_per_layer((object->layers().size() + 1) / 2);
- tbb::parallel_for(tbb::blocked_range(0, object->layers().size() / 2),
- [&object, &polygons_per_layer](const tbb::blocked_range &range) {
- for (size_t i = range.begin(); i < range.end(); ++ i) {
- const Layer* layer1 = object->layers()[i * 2];
- const Layer* layer2 = object->layers()[i * 2 + 1];
- Polygons polys;
- polys.reserve(layer1->lslices.size() + layer2->lslices.size());
- for (const ExPolygon &expoly : layer1->lslices)
- //FIXME no holes?
- polys.emplace_back(expoly.contour);
- for (const ExPolygon &expoly : layer2->lslices)
- //FIXME no holes?
- polys.emplace_back(expoly.contour);
- polygons_per_layer[i] = union_(polys);
- }
- });
- if (object->layers().size() & 1) {
- const Layer *layer = object->layers().back();
- Polygons polys;
- polys.reserve(layer->lslices.size());
- for (const ExPolygon &expoly : layer->lslices)
- //FIXME no holes?
- polys.emplace_back(expoly.contour);
- polygons_per_layer.back() = union_(polys);
- }
- // Now reduce down to a single layer.
- size_t cnt = polygons_per_layer.size();
- while (cnt > 1) {
- tbb::parallel_for(tbb::blocked_range(0, cnt / 2),
- [&polygons_per_layer](const tbb::blocked_range &range) {
- for (size_t i = range.begin(); i < range.end(); ++ i) {
- Polygons polys;
- polys.reserve(polygons_per_layer[i * 2].size() + polygons_per_layer[i * 2 + 1].size());
- polygons_append(polys, polygons_per_layer[i * 2]);
- polygons_append(polys, polygons_per_layer[i * 2 + 1]);
- polygons_per_layer[i * 2] = union_(polys);
- }
- });
- for (size_t i = 0; i < cnt / 2; ++ i)
- polygons_per_layer[i] = std::move(polygons_per_layer[i * 2]);
- if (cnt & 1)
- polygons_per_layer[cnt / 2] = std::move(polygons_per_layer[cnt - 1]);
- cnt = (cnt + 1) / 2;
- }
- // And collect copies of the objects.
- for (const PrintInstance &instance : object->instances()) {
- // All the layers were reduced to the 1st item of polygons_per_layer.
- size_t i = islands.size();
- polygons_append(islands, polygons_per_layer.front());
- for (; i < islands.size(); ++ i)
- islands[i].translate(instance.shift);
- }
- }
- return islands;
-}
-
-std::string OozePrevention::pre_toolchange(GCode &gcodegen)
-{
- std::string gcode;
-
- // move to the nearest standby point
- if (!this->standby_points.empty()) {
- // get current position in print coordinates
- Vec3d writer_pos = gcodegen.writer().get_position();
- Point pos = Point::new_scale(writer_pos(0), writer_pos(1));
-
- // find standby point
- Point standby_point;
- pos.nearest_point(this->standby_points, &standby_point);
-
- /* We don't call gcodegen.travel_to() because we don't need retraction (it was already
- triggered by the caller) nor avoid_crossing_perimeters and also because the coordinates
- of the destination point must not be transformed by origin nor current extruder offset. */
- gcode += gcodegen.writer().travel_to_xy(unscale(standby_point),
- "move to standby position");
- }
-
- if (gcodegen.config().standby_temperature_delta.value != 0) {
- // we assume that heating is always slower than cooling, so no need to block
- gcode += gcodegen.writer().set_temperature
- (this->_get_temp(gcodegen) + gcodegen.config().standby_temperature_delta.value, false, gcodegen.writer().extruder()->id());
- }
-
- return gcode;
-}
-
-std::string OozePrevention::post_toolchange(GCode &gcodegen)
-{
- return (gcodegen.config().standby_temperature_delta.value != 0) ?
- gcodegen.writer().set_temperature(this->_get_temp(gcodegen), true, gcodegen.writer().extruder()->id()) :
- std::string();
-}
-
-int
-OozePrevention::_get_temp(GCode &gcodegen)
-{
- return (gcodegen.layer() != NULL && gcodegen.layer()->id() == 0)
- ? gcodegen.config().first_layer_temperature.get_at(gcodegen.writer().extruder()->id())
- : gcodegen.config().temperature.get_at(gcodegen.writer().extruder()->id());
-}
-
-std::string Wipe::wipe(GCode &gcodegen, bool toolchange)
-{
- std::string gcode;
-
- /* Reduce feedrate a bit; travel speed is often too high to move on existing material.
- Too fast = ripping of existing material; too slow = short wipe path, thus more blob. */
- double wipe_speed = gcodegen.writer().config.travel_speed.value * 0.8;
-
- // get the retraction length
- double length = toolchange
- ? gcodegen.writer().extruder()->retract_length_toolchange()
- : gcodegen.writer().extruder()->retract_length();
- // Shorten the retraction length by the amount already retracted before wipe.
- length *= (1. - gcodegen.writer().extruder()->retract_before_wipe());
-
- if (length > 0) {
- /* Calculate how long we need to travel in order to consume the required
- amount of retraction. In other words, how far do we move in XY at wipe_speed
- for the time needed to consume retract_length at retract_speed? */
- double wipe_dist = scale_(length / gcodegen.writer().extruder()->retract_speed() * wipe_speed);
-
- /* Take the stored wipe path and replace first point with the current actual position
- (they might be different, for example, in case of loop clipping). */
- Polyline wipe_path;
- wipe_path.append(gcodegen.last_pos());
- wipe_path.append(
- this->path.points.begin() + 1,
- this->path.points.end()
- );
-
- wipe_path.clip_end(wipe_path.length() - wipe_dist);
-
- // subdivide the retraction in segments
- if (! wipe_path.empty()) {
- for (const Line &line : wipe_path.lines()) {
- double segment_length = line.length();
- /* Reduce retraction length a bit to avoid effective retraction speed to be greater than the configured one
- due to rounding (TODO: test and/or better math for this) */
- double dE = length * (segment_length / wipe_dist) * 0.95;
- //FIXME one shall not generate the unnecessary G1 Fxxx commands, here wipe_speed is a constant inside this cycle.
- // Is it here for the cooling markers? Or should it be outside of the cycle?
- gcode += gcodegen.writer().set_speed(wipe_speed*60, "", gcodegen.enable_cooling_markers() ? ";_WIPE" : "");
- gcode += gcodegen.writer().extrude_to_xy(
- gcodegen.point_to_gcode(line.b),
- -dE,
- "wipe and retract"
- );
+ // Return true if tch_prefix is found in custom_gcode
+ static bool custom_gcode_changes_tool(const std::string& custom_gcode, const std::string& tch_prefix, unsigned next_extruder)
+ {
+ bool ok = false;
+ size_t from_pos = 0;
+ size_t pos = 0;
+ while ((pos = custom_gcode.find(tch_prefix, from_pos)) != std::string::npos) {
+ if (pos + 1 == custom_gcode.size())
+ break;
+ from_pos = pos + 1;
+ // only whitespace is allowed before the command
+ while (--pos < custom_gcode.size() && custom_gcode[pos] != '\n') {
+ if (!std::isspace(custom_gcode[pos]))
+ goto NEXT;
}
- gcodegen.set_last_pos(wipe_path.points.back());
+ {
+ // we should also check that the extruder changes to what was expected
+ std::istringstream ss(custom_gcode.substr(from_pos, std::string::npos));
+ unsigned num = 0;
+ if (ss >> num)
+ ok = (num == next_extruder);
+ }
+ NEXT:;
}
-
- // prevent wiping again on same path
- this->reset_path();
- }
-
- return gcode;
-}
-
-static inline Point wipe_tower_point_to_object_point(GCode &gcodegen, const Vec2f &wipe_tower_pt)
-{
- return Point(scale_(wipe_tower_pt.x() - gcodegen.origin()(0)), scale_(wipe_tower_pt.y() - gcodegen.origin()(1)));
-}
-
-std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z) const
-{
- if (new_extruder_id != -1 && new_extruder_id != tcr.new_tool)
- throw std::invalid_argument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect.");
-
- std::string gcode;
-
- // Toolchangeresult.gcode assumes the wipe tower corner is at the origin (except for priming lines)
- // We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position
- float alpha = m_wipe_tower_rotation/180.f * float(M_PI);
- Vec2f start_pos = tcr.start_pos;
- Vec2f end_pos = tcr.end_pos;
- if (!tcr.priming) {
- start_pos = Eigen::Rotation2Df(alpha) * start_pos;
- start_pos += m_wipe_tower_pos;
- end_pos = Eigen::Rotation2Df(alpha) * end_pos;
- end_pos += m_wipe_tower_pos;
+ return ok;
}
- Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : m_wipe_tower_pos;
- float wipe_tower_rotation = tcr.priming ? 0.f : alpha;
-
- std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation);
-
- if (!tcr.priming) {
- // Move over the wipe tower.
- // Retract for a tool change, using the toolchange retract value and setting the priming extra length.
- gcode += gcodegen.retract(true);
- gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true;
- gcode += gcodegen.travel_to(
- wipe_tower_point_to_object_point(gcodegen, start_pos),
- erMixed,
- "Travel to a Wipe Tower");
- gcode += gcodegen.unretract();
+ void AvoidCrossingPerimeters::init_external_mp(const Print& print)
+ {
+ m_external_mp = Slic3r::make_unique(union_ex(this->collect_contours_all_layers(print.objects())));
}
- double current_z = gcodegen.writer().get_position().z();
- if (z == -1.) // in case no specific z was provided, print at current_z pos
- z = current_z;
- if (! is_approx(z, current_z)) {
- gcode += gcodegen.writer().retract();
- gcode += gcodegen.writer().travel_to_z(z, "Travel down to the last wipe tower layer.");
- gcode += gcodegen.writer().unretract();
+ // Plan a travel move while minimizing the number of perimeter crossings.
+ // point is in unscaled coordinates, in the coordinate system of the current active object
+ // (set by gcodegen.set_origin()).
+ Polyline AvoidCrossingPerimeters::travel_to(const GCode& gcodegen, const Point& point)
+ {
+ // If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset).
+ // Otherwise perform the path planning in the coordinate system of the active object.
+ bool use_external = this->use_external_mp || this->use_external_mp_once;
+ Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0);
+ Polyline result = (use_external ? m_external_mp.get() : m_layer_mp.get())->
+ shortest_path(gcodegen.last_pos() + scaled_origin, point + scaled_origin);
+ if (use_external)
+ result.translate(-scaled_origin);
+ return result;
}
-
- // Process the end filament gcode.
- std::string end_filament_gcode_str;
- if (gcodegen.writer().extruder() != nullptr) {
- // Process the custom end_filament_gcode in case of single_extruder_multi_material.
- unsigned int old_extruder_id = gcodegen.writer().extruder()->id();
- const std::string &end_filament_gcode = gcodegen.config().end_filament_gcode.get_at(old_extruder_id);
- if (gcodegen.writer().extruder() != nullptr && ! end_filament_gcode.empty()) {
- end_filament_gcode_str = gcodegen.placeholder_parser_process("end_filament_gcode", end_filament_gcode, old_extruder_id);
- check_add_eol(end_filament_gcode_str);
+ // Collect outer contours of all objects over all layers.
+ // Discard objects only containing thin walls (offset would fail on an empty polygon).
+ // Used by avoid crossing perimeters feature.
+ Polygons AvoidCrossingPerimeters::collect_contours_all_layers(const PrintObjectPtrs& objects)
+ {
+ Polygons islands;
+ for (const PrintObject* object : objects) {
+ // Reducing all the object slices into the Z projection in a logarithimc fashion.
+ // First reduce to half the number of layers.
+ std::vector polygons_per_layer((object->layers().size() + 1) / 2);
+ tbb::parallel_for(tbb::blocked_range(0, object->layers().size() / 2),
+ [&object, &polygons_per_layer](const tbb::blocked_range& range) {
+ for (size_t i = range.begin(); i < range.end(); ++i) {
+ const Layer* layer1 = object->layers()[i * 2];
+ const Layer* layer2 = object->layers()[i * 2 + 1];
+ Polygons polys;
+ polys.reserve(layer1->lslices.size() + layer2->lslices.size());
+ for (const ExPolygon& expoly : layer1->lslices)
+ //FIXME no holes?
+ polys.emplace_back(expoly.contour);
+ for (const ExPolygon& expoly : layer2->lslices)
+ //FIXME no holes?
+ polys.emplace_back(expoly.contour);
+ polygons_per_layer[i] = union_(polys);
+ }
+ });
+ if (object->layers().size() & 1) {
+ const Layer* layer = object->layers().back();
+ Polygons polys;
+ polys.reserve(layer->lslices.size());
+ for (const ExPolygon& expoly : layer->lslices)
+ //FIXME no holes?
+ polys.emplace_back(expoly.contour);
+ polygons_per_layer.back() = union_(polys);
+ }
+ // Now reduce down to a single layer.
+ size_t cnt = polygons_per_layer.size();
+ while (cnt > 1) {
+ tbb::parallel_for(tbb::blocked_range(0, cnt / 2),
+ [&polygons_per_layer](const tbb::blocked_range& range) {
+ for (size_t i = range.begin(); i < range.end(); ++i) {
+ Polygons polys;
+ polys.reserve(polygons_per_layer[i * 2].size() + polygons_per_layer[i * 2 + 1].size());
+ polygons_append(polys, polygons_per_layer[i * 2]);
+ polygons_append(polys, polygons_per_layer[i * 2 + 1]);
+ polygons_per_layer[i * 2] = union_(polys);
+ }
+ });
+ for (size_t i = 0; i < cnt / 2; ++i)
+ polygons_per_layer[i] = std::move(polygons_per_layer[i * 2]);
+ if (cnt & 1)
+ polygons_per_layer[cnt / 2] = std::move(polygons_per_layer[cnt - 1]);
+ cnt = (cnt + 1) / 2;
+ }
+ // And collect copies of the objects.
+ for (const PrintInstance& instance : object->instances()) {
+ // All the layers were reduced to the 1st item of polygons_per_layer.
+ size_t i = islands.size();
+ polygons_append(islands, polygons_per_layer.front());
+ for (; i < islands.size(); ++i)
+ islands[i].translate(instance.shift);
+ }
}
+ return islands;
}
- // Process the custom toolchange_gcode. If it is empty, provide a simple Tn command to change the filament.
- // Otherwise, leave control to the user completely.
- std::string toolchange_gcode_str;
- if (true /*gcodegen.writer().extruder() != nullptr*/) {
- const std::string& toolchange_gcode = gcodegen.config().toolchange_gcode.value;
- if (!toolchange_gcode.empty()) {
+ std::string OozePrevention::pre_toolchange(GCode& gcodegen)
+ {
+ std::string gcode;
+
+ // move to the nearest standby point
+ if (!this->standby_points.empty()) {
+ // get current position in print coordinates
+ Vec3d writer_pos = gcodegen.writer().get_position();
+ Point pos = Point::new_scale(writer_pos(0), writer_pos(1));
+
+ // find standby point
+ Point standby_point;
+ pos.nearest_point(this->standby_points, &standby_point);
+
+ /* We don't call gcodegen.travel_to() because we don't need retraction (it was already
+ triggered by the caller) nor avoid_crossing_perimeters and also because the coordinates
+ of the destination point must not be transformed by origin nor current extruder offset. */
+ gcode += gcodegen.writer().travel_to_xy(unscale(standby_point),
+ "move to standby position");
+ }
+
+ if (gcodegen.config().standby_temperature_delta.value != 0) {
+ // we assume that heating is always slower than cooling, so no need to block
+ gcode += gcodegen.writer().set_temperature
+ (this->_get_temp(gcodegen) + gcodegen.config().standby_temperature_delta.value, false, gcodegen.writer().extruder()->id());
+ }
+
+ return gcode;
+ }
+
+ std::string OozePrevention::post_toolchange(GCode& gcodegen)
+ {
+ return (gcodegen.config().standby_temperature_delta.value != 0) ?
+ gcodegen.writer().set_temperature(this->_get_temp(gcodegen), true, gcodegen.writer().extruder()->id()) :
+ std::string();
+ }
+
+ int
+ OozePrevention::_get_temp(GCode& gcodegen)
+ {
+ return (gcodegen.layer() != NULL && gcodegen.layer()->id() == 0)
+ ? gcodegen.config().first_layer_temperature.get_at(gcodegen.writer().extruder()->id())
+ : gcodegen.config().temperature.get_at(gcodegen.writer().extruder()->id());
+ }
+
+ std::string Wipe::wipe(GCode& gcodegen, bool toolchange)
+ {
+ std::string gcode;
+
+ /* Reduce feedrate a bit; travel speed is often too high to move on existing material.
+ Too fast = ripping of existing material; too slow = short wipe path, thus more blob. */
+ double wipe_speed = gcodegen.writer().config.travel_speed.value * 0.8;
+
+ // get the retraction length
+ double length = toolchange
+ ? gcodegen.writer().extruder()->retract_length_toolchange()
+ : gcodegen.writer().extruder()->retract_length();
+ // Shorten the retraction length by the amount already retracted before wipe.
+ length *= (1. - gcodegen.writer().extruder()->retract_before_wipe());
+
+ if (length > 0) {
+ /* Calculate how long we need to travel in order to consume the required
+ amount of retraction. In other words, how far do we move in XY at wipe_speed
+ for the time needed to consume retract_length at retract_speed? */
+ double wipe_dist = scale_(length / gcodegen.writer().extruder()->retract_speed() * wipe_speed);
+
+ /* Take the stored wipe path and replace first point with the current actual position
+ (they might be different, for example, in case of loop clipping). */
+ Polyline wipe_path;
+ wipe_path.append(gcodegen.last_pos());
+ wipe_path.append(
+ this->path.points.begin() + 1,
+ this->path.points.end()
+ );
+
+ wipe_path.clip_end(wipe_path.length() - wipe_dist);
+
+ // subdivide the retraction in segments
+ if (!wipe_path.empty()) {
+ for (const Line& line : wipe_path.lines()) {
+ double segment_length = line.length();
+ /* Reduce retraction length a bit to avoid effective retraction speed to be greater than the configured one
+ due to rounding (TODO: test and/or better math for this) */
+ double dE = length * (segment_length / wipe_dist) * 0.95;
+ //FIXME one shall not generate the unnecessary G1 Fxxx commands, here wipe_speed is a constant inside this cycle.
+ // Is it here for the cooling markers? Or should it be outside of the cycle?
+ gcode += gcodegen.writer().set_speed(wipe_speed * 60, "", gcodegen.enable_cooling_markers() ? ";_WIPE" : "");
+ gcode += gcodegen.writer().extrude_to_xy(
+ gcodegen.point_to_gcode(line.b),
+ -dE,
+ "wipe and retract"
+ );
+ }
+ gcodegen.set_last_pos(wipe_path.points.back());
+ }
+
+ // prevent wiping again on same path
+ this->reset_path();
+ }
+
+ return gcode;
+ }
+
+ static inline Point wipe_tower_point_to_object_point(GCode& gcodegen, const Vec2f& wipe_tower_pt)
+ {
+ return Point(scale_(wipe_tower_pt.x() - gcodegen.origin()(0)), scale_(wipe_tower_pt.y() - gcodegen.origin()(1)));
+ }
+
+ std::string WipeTowerIntegration::append_tcr(GCode& gcodegen, const WipeTower::ToolChangeResult& tcr, int new_extruder_id, double z) const
+ {
+ if (new_extruder_id != -1 && new_extruder_id != tcr.new_tool)
+ throw std::invalid_argument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect.");
+
+ std::string gcode;
+
+ // Toolchangeresult.gcode assumes the wipe tower corner is at the origin (except for priming lines)
+ // We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position
+ float alpha = m_wipe_tower_rotation / 180.f * float(M_PI);
+ Vec2f start_pos = tcr.start_pos;
+ Vec2f end_pos = tcr.end_pos;
+ if (!tcr.priming) {
+ start_pos = Eigen::Rotation2Df(alpha) * start_pos;
+ start_pos += m_wipe_tower_pos;
+ end_pos = Eigen::Rotation2Df(alpha) * end_pos;
+ end_pos += m_wipe_tower_pos;
+ }
+
+ Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : m_wipe_tower_pos;
+ float wipe_tower_rotation = tcr.priming ? 0.f : alpha;
+
+ std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation);
+
+ if (!tcr.priming) {
+ // Move over the wipe tower.
+ // Retract for a tool change, using the toolchange retract value and setting the priming extra length.
+ gcode += gcodegen.retract(true);
+ gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true;
+ gcode += gcodegen.travel_to(
+ wipe_tower_point_to_object_point(gcodegen, start_pos),
+ erMixed,
+ "Travel to a Wipe Tower");
+ gcode += gcodegen.unretract();
+ }
+
+ double current_z = gcodegen.writer().get_position().z();
+ if (z == -1.) // in case no specific z was provided, print at current_z pos
+ z = current_z;
+ if (!is_approx(z, current_z)) {
+ gcode += gcodegen.writer().retract();
+ gcode += gcodegen.writer().travel_to_z(z, "Travel down to the last wipe tower layer.");
+ gcode += gcodegen.writer().unretract();
+ }
+
+
+ // Process the end filament gcode.
+ std::string end_filament_gcode_str;
+ if (gcodegen.writer().extruder() != nullptr) {
+ // Process the custom end_filament_gcode in case of single_extruder_multi_material.
+ unsigned int old_extruder_id = gcodegen.writer().extruder()->id();
+ const std::string& end_filament_gcode = gcodegen.config().end_filament_gcode.get_at(old_extruder_id);
+ if (gcodegen.writer().extruder() != nullptr && !end_filament_gcode.empty()) {
+ end_filament_gcode_str = gcodegen.placeholder_parser_process("end_filament_gcode", end_filament_gcode, old_extruder_id);
+ check_add_eol(end_filament_gcode_str);
+ }
+ }
+
+ // Process the custom toolchange_gcode. If it is empty, provide a simple Tn command to change the filament.
+ // Otherwise, leave control to the user completely.
+ std::string toolchange_gcode_str;
+ if (true /*gcodegen.writer().extruder() != nullptr*/) {
+ const std::string& toolchange_gcode = gcodegen.config().toolchange_gcode.value;
+ if (!toolchange_gcode.empty()) {
+ DynamicConfig config;
+ int previous_extruder_id = gcodegen.writer().extruder() ? (int)gcodegen.writer().extruder()->id() : -1;
+ config.set_key_value("previous_extruder", new ConfigOptionInt(previous_extruder_id));
+ config.set_key_value("next_extruder", new ConfigOptionInt((int)new_extruder_id));
+ config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index));
+ config.set_key_value("layer_z", new ConfigOptionFloat(tcr.print_z));
+ toolchange_gcode_str = gcodegen.placeholder_parser_process("toolchange_gcode", toolchange_gcode, new_extruder_id, &config);
+ check_add_eol(toolchange_gcode_str);
+ }
+
+ std::string toolchange_command;
+ if (tcr.priming || (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id)))
+ toolchange_command = gcodegen.writer().toolchange(new_extruder_id);
+ if (!custom_gcode_changes_tool(toolchange_gcode_str, gcodegen.writer().toolchange_prefix(), new_extruder_id))
+ toolchange_gcode_str += toolchange_command;
+ else {
+ // We have informed the m_writer about the current extruder_id, we can ignore the generated G-code.
+ }
+ }
+
+ gcodegen.placeholder_parser().set("current_extruder", new_extruder_id);
+
+ // Process the start filament gcode.
+ std::string start_filament_gcode_str;
+ const std::string& start_filament_gcode = gcodegen.config().start_filament_gcode.get_at(new_extruder_id);
+ if (!start_filament_gcode.empty()) {
+ // Process the start_filament_gcode for the active filament only.
DynamicConfig config;
- int previous_extruder_id = gcodegen.writer().extruder() ? (int)gcodegen.writer().extruder()->id() : -1;
- config.set_key_value("previous_extruder", new ConfigOptionInt(previous_extruder_id));
- config.set_key_value("next_extruder", new ConfigOptionInt((int)new_extruder_id));
- config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index));
- config.set_key_value("layer_z", new ConfigOptionFloat(tcr.print_z));
- toolchange_gcode_str = gcodegen.placeholder_parser_process("toolchange_gcode", toolchange_gcode, new_extruder_id, &config);
- check_add_eol(toolchange_gcode_str);
+ config.set_key_value("filament_extruder_id", new ConfigOptionInt(new_extruder_id));
+ start_filament_gcode_str = gcodegen.placeholder_parser_process("start_filament_gcode", start_filament_gcode, new_extruder_id, &config);
+ check_add_eol(start_filament_gcode_str);
}
- std::string toolchange_command;
- if (tcr.priming || (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id)))
- toolchange_command = gcodegen.writer().toolchange(new_extruder_id);
- if (! custom_gcode_changes_tool(toolchange_gcode_str, gcodegen.writer().toolchange_prefix(), new_extruder_id))
- toolchange_gcode_str += toolchange_command;
- else {
- // We have informed the m_writer about the current extruder_id, we can ignore the generated G-code.
- }
- }
-
- gcodegen.placeholder_parser().set("current_extruder", new_extruder_id);
-
- // Process the start filament gcode.
- std::string start_filament_gcode_str;
- const std::string &start_filament_gcode = gcodegen.config().start_filament_gcode.get_at(new_extruder_id);
- if (! start_filament_gcode.empty()) {
- // Process the start_filament_gcode for the active filament only.
+ // Insert the end filament, toolchange, and start filament gcode into the generated gcode.
DynamicConfig config;
- config.set_key_value("filament_extruder_id", new ConfigOptionInt(new_extruder_id));
- start_filament_gcode_str = gcodegen.placeholder_parser_process("start_filament_gcode", start_filament_gcode, new_extruder_id, &config);
- check_add_eol(start_filament_gcode_str);
+ config.set_key_value("end_filament_gcode", new ConfigOptionString(end_filament_gcode_str));
+ config.set_key_value("toolchange_gcode", new ConfigOptionString(toolchange_gcode_str));
+ config.set_key_value("start_filament_gcode", new ConfigOptionString(start_filament_gcode_str));
+ std::string tcr_gcode, tcr_escaped_gcode = gcodegen.placeholder_parser_process("tcr_rotated_gcode", tcr_rotated_gcode, new_extruder_id, &config);
+ unescape_string_cstyle(tcr_escaped_gcode, tcr_gcode);
+ gcode += tcr_gcode;
+ check_add_eol(toolchange_gcode_str);
+
+
+ // A phony move to the end position at the wipe tower.
+ gcodegen.writer().travel_to_xy(end_pos.cast());
+ gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos));
+ if (!is_approx(z, current_z)) {
+ gcode += gcodegen.writer().retract();
+ gcode += gcodegen.writer().travel_to_z(current_z, "Travel back up to the topmost object layer.");
+ gcode += gcodegen.writer().unretract();
+ }
+
+ else {
+ // Prepare a future wipe.
+ gcodegen.m_wipe.path.points.clear();
+ if (new_extruder_id >= 0) {
+ // Start the wipe at the current position.
+ gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, end_pos));
+ // Wipe end point: Wipe direction away from the closer tower edge to the further tower edge.
+ gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen,
+ Vec2f((std::abs(m_left - end_pos.x()) < std::abs(m_right - end_pos.x())) ? m_right : m_left,
+ end_pos.y())));
+ }
+ }
+
+ // Let the planner know we are traveling between objects.
+ gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true;
+ return gcode;
}
- // Insert the end filament, toolchange, and start filament gcode into the generated gcode.
- DynamicConfig config;
- config.set_key_value("end_filament_gcode", new ConfigOptionString(end_filament_gcode_str));
- config.set_key_value("toolchange_gcode", new ConfigOptionString(toolchange_gcode_str));
- config.set_key_value("start_filament_gcode", new ConfigOptionString(start_filament_gcode_str));
- std::string tcr_gcode, tcr_escaped_gcode = gcodegen.placeholder_parser_process("tcr_rotated_gcode", tcr_rotated_gcode, new_extruder_id, &config);
- unescape_string_cstyle(tcr_escaped_gcode, tcr_gcode);
- gcode += tcr_gcode;
- check_add_eol(toolchange_gcode_str);
+ // This function postprocesses gcode_original, rotates and moves all G1 extrusions and returns resulting gcode
+ // Starting position has to be supplied explicitely (otherwise it would fail in case first G1 command only contained one coordinate)
+ std::string WipeTowerIntegration::post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const
+ {
+ Vec2f extruder_offset = m_extruder_offsets[tcr.initial_tool].cast();
+ std::istringstream gcode_str(tcr.gcode);
+ std::string gcode_out;
+ std::string line;
+ Vec2f pos = tcr.start_pos;
+ Vec2f transformed_pos = pos;
+ Vec2f old_pos(-1000.1f, -1000.1f);
- // A phony move to the end position at the wipe tower.
- gcodegen.writer().travel_to_xy(end_pos.cast());
- gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos));
- if (! is_approx(z, current_z)) {
- gcode += gcodegen.writer().retract();
- gcode += gcodegen.writer().travel_to_z(current_z, "Travel back up to the topmost object layer.");
- gcode += gcodegen.writer().unretract();
+ while (gcode_str) {
+ std::getline(gcode_str, line); // we read the gcode line by line
+
+ // All G1 commands should be translated and rotated. X and Y coords are
+ // only pushed to the output when they differ from last time.
+ // WT generator can override this by appending the never_skip_tag
+ if (line.find("G1 ") == 0) {
+ bool never_skip = false;
+ auto it = line.find(WipeTower::never_skip_tag());
+ if (it != std::string::npos) {
+ // remove the tag and remember we saw it
+ never_skip = true;
+ line.erase(it, it + WipeTower::never_skip_tag().size());
+ }
+ std::ostringstream line_out;
+ std::istringstream line_str(line);
+ line_str >> std::noskipws; // don't skip whitespace
+ char ch = 0;
+ while (line_str >> ch) {
+ if (ch == 'X' || ch == 'Y')
+ line_str >> (ch == 'X' ? pos.x() : pos.y());
+ else
+ line_out << ch;
+ }
+
+ transformed_pos = Eigen::Rotation2Df(angle) * pos + translation;
+
+ if (transformed_pos != old_pos || never_skip) {
+ line = line_out.str();
+ std::ostringstream oss;
+ oss << std::fixed << std::setprecision(3) << "G1 ";
+ if (transformed_pos.x() != old_pos.x() || never_skip)
+ oss << " X" << transformed_pos.x() - extruder_offset.x();
+ if (transformed_pos.y() != old_pos.y() || never_skip)
+ oss << " Y" << transformed_pos.y() - extruder_offset.y();
+ oss << " ";
+ line.replace(line.find("G1 "), 3, oss.str());
+ old_pos = transformed_pos;
+ }
+ }
+
+ gcode_out += line + "\n";
+
+ // If this was a toolchange command, we should change current extruder offset
+ if (line == "[toolchange_gcode]") {
+ extruder_offset = m_extruder_offsets[tcr.new_tool].cast();
+
+ // If the extruder offset changed, add an extra move so everything is continuous
+ if (extruder_offset != m_extruder_offsets[tcr.initial_tool].cast()) {
+ std::ostringstream oss;
+ oss << std::fixed << std::setprecision(3)
+ << "G1 X" << transformed_pos.x() - extruder_offset.x()
+ << " Y" << transformed_pos.y() - extruder_offset.y()
+ << "\n";
+ gcode_out += oss.str();
+ }
+ }
+ }
+ return gcode_out;
}
- else {
+
+ std::string WipeTowerIntegration::prime(GCode& gcodegen)
+ {
+ assert(m_layer_idx == 0);
+ std::string gcode;
+
+
+ // Disable linear advance for the wipe tower operations.
+ //gcode += (gcodegen.config().gcode_flavor == gcfRepRap ? std::string("M572 D0 S0\n") : std::string("M900 K0\n"));
+
+ for (const WipeTower::ToolChangeResult& tcr : m_priming) {
+ if (!tcr.extrusions.empty())
+ gcode += append_tcr(gcodegen, tcr, tcr.new_tool);
+
+
+ // Let the tool change be executed by the wipe tower class.
+ // Inform the G-code writer about the changes done behind its back.
+ //gcode += tcr.gcode;
+ // Let the m_writer know the current extruder_id, but ignore the generated G-code.
+ // unsigned int current_extruder_id = tcr.extrusions.back().tool;
+ // gcodegen.writer().toolchange(current_extruder_id);
+ // gcodegen.placeholder_parser().set("current_extruder", current_extruder_id);
+
+ }
+
+ // A phony move to the end position at the wipe tower.
+ /* gcodegen.writer().travel_to_xy(Vec2d(m_priming.back().end_pos.x, m_priming.back().end_pos.y));
+ gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, m_priming.back().end_pos));
// Prepare a future wipe.
gcodegen.m_wipe.path.points.clear();
- if (new_extruder_id >= 0) {
- // Start the wipe at the current position.
- gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, end_pos));
- // Wipe end point: Wipe direction away from the closer tower edge to the further tower edge.
- gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen,
- Vec2f((std::abs(m_left - end_pos.x()) < std::abs(m_right - end_pos.x())) ? m_right : m_left,
- end_pos.y())));
+ // Start the wipe at the current position.
+ gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, m_priming.back().end_pos));
+ // Wipe end point: Wipe direction away from the closer tower edge to the further tower edge.
+ gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen,
+ WipeTower::xy((std::abs(m_left - m_priming.back().end_pos.x) < std::abs(m_right - m_priming.back().end_pos.x)) ? m_right : m_left,
+ m_priming.back().end_pos.y)));*/
+
+ return gcode;
+ }
+
+ std::string WipeTowerIntegration::tool_change(GCode& gcodegen, int extruder_id, bool finish_layer)
+ {
+ std::string gcode;
+ assert(m_layer_idx >= 0);
+ if (!m_brim_done || gcodegen.writer().need_toolchange(extruder_id) || finish_layer) {
+ if (m_layer_idx < (int)m_tool_changes.size()) {
+ if (!(size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size()))
+ throw std::runtime_error("Wipe tower generation failed, possibly due to empty first layer.");
+
+
+ // Calculate where the wipe tower layer will be printed. -1 means that print z will not change,
+ // resulting in a wipe tower with sparse layers.
+ double wipe_tower_z = -1;
+ bool ignore_sparse = false;
+ if (gcodegen.config().wipe_tower_no_sparse_layers.value) {
+ wipe_tower_z = m_last_wipe_tower_print_z;
+ ignore_sparse = (m_brim_done && m_tool_changes[m_layer_idx].size() == 1 && m_tool_changes[m_layer_idx].front().initial_tool == m_tool_changes[m_layer_idx].front().new_tool);
+ if (m_tool_change_idx == 0 && !ignore_sparse)
+ wipe_tower_z = m_last_wipe_tower_print_z + m_tool_changes[m_layer_idx].front().layer_height;
+ }
+
+ if (!ignore_sparse) {
+ gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][m_tool_change_idx++], extruder_id, wipe_tower_z);
+ m_last_wipe_tower_print_z = wipe_tower_z;
+ }
+ }
+ m_brim_done = true;
}
+ return gcode;
}
- // Let the planner know we are traveling between objects.
- gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true;
- return gcode;
-}
-
-// This function postprocesses gcode_original, rotates and moves all G1 extrusions and returns resulting gcode
-// Starting position has to be supplied explicitely (otherwise it would fail in case first G1 command only contained one coordinate)
-std::string WipeTowerIntegration::post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const
-{
- Vec2f extruder_offset = m_extruder_offsets[tcr.initial_tool].cast();
-
- std::istringstream gcode_str(tcr.gcode);
- std::string gcode_out;
- std::string line;
- Vec2f pos = tcr.start_pos;
- Vec2f transformed_pos = pos;
- Vec2f old_pos(-1000.1f, -1000.1f);
-
- while (gcode_str) {
- std::getline(gcode_str, line); // we read the gcode line by line
-
- // All G1 commands should be translated and rotated. X and Y coords are
- // only pushed to the output when they differ from last time.
- // WT generator can override this by appending the never_skip_tag
- if (line.find("G1 ") == 0) {
- bool never_skip = false;
- auto it = line.find(WipeTower::never_skip_tag());
- if (it != std::string::npos) {
- // remove the tag and remember we saw it
- never_skip = true;
- line.erase(it, it+WipeTower::never_skip_tag().size());
- }
- std::ostringstream line_out;
- std::istringstream line_str(line);
- line_str >> std::noskipws; // don't skip whitespace
- char ch = 0;
- while (line_str >> ch) {
- if (ch == 'X' || ch =='Y')
- line_str >> (ch == 'X' ? pos.x() : pos.y());
- else
- line_out << ch;
- }
-
- transformed_pos = Eigen::Rotation2Df(angle) * pos + translation;
-
- if (transformed_pos != old_pos || never_skip) {
- line = line_out.str();
- std::ostringstream oss;
- oss << std::fixed << std::setprecision(3) << "G1 ";
- if (transformed_pos.x() != old_pos.x() || never_skip)
- oss << " X" << transformed_pos.x() - extruder_offset.x();
- if (transformed_pos.y() != old_pos.y() || never_skip)
- oss << " Y" << transformed_pos.y() - extruder_offset.y();
- oss << " ";
- line.replace(line.find("G1 "), 3, oss.str());
- old_pos = transformed_pos;
- }
- }
-
- gcode_out += line + "\n";
-
- // If this was a toolchange command, we should change current extruder offset
- if (line == "[toolchange_gcode]") {
- extruder_offset = m_extruder_offsets[tcr.new_tool].cast();
-
- // If the extruder offset changed, add an extra move so everything is continuous
- if (extruder_offset != m_extruder_offsets[tcr.initial_tool].cast()) {
- std::ostringstream oss;
- oss << std::fixed << std::setprecision(3)
- << "G1 X" << transformed_pos.x() - extruder_offset.x()
- << " Y" << transformed_pos.y() - extruder_offset.y()
- << "\n";
- gcode_out += oss.str();
- }
- }
- }
- return gcode_out;
-}
-
-
-std::string WipeTowerIntegration::prime(GCode &gcodegen)
-{
- assert(m_layer_idx == 0);
- std::string gcode;
-
-
- // Disable linear advance for the wipe tower operations.
- //gcode += (gcodegen.config().gcode_flavor == gcfRepRap ? std::string("M572 D0 S0\n") : std::string("M900 K0\n"));
-
- for (const WipeTower::ToolChangeResult& tcr : m_priming) {
- if (!tcr.extrusions.empty())
- gcode += append_tcr(gcodegen, tcr, tcr.new_tool);
-
-
- // Let the tool change be executed by the wipe tower class.
- // Inform the G-code writer about the changes done behind its back.
- //gcode += tcr.gcode;
- // Let the m_writer know the current extruder_id, but ignore the generated G-code.
- // unsigned int current_extruder_id = tcr.extrusions.back().tool;
- // gcodegen.writer().toolchange(current_extruder_id);
- // gcodegen.placeholder_parser().set("current_extruder", current_extruder_id);
-
+ // Print is finished. Now it remains to unload the filament safely with ramming over the wipe tower.
+ std::string WipeTowerIntegration::finalize(GCode& gcodegen)
+ {
+ std::string gcode;
+ if (std::abs(gcodegen.writer().get_position()(2) - m_final_purge.print_z) > EPSILON)
+ gcode += gcodegen.change_layer(m_final_purge.print_z);
+ gcode += append_tcr(gcodegen, m_final_purge, -1);
+ return gcode;
}
- // A phony move to the end position at the wipe tower.
- /* gcodegen.writer().travel_to_xy(Vec2d(m_priming.back().end_pos.x, m_priming.back().end_pos.y));
- gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, m_priming.back().end_pos));
- // Prepare a future wipe.
- gcodegen.m_wipe.path.points.clear();
- // Start the wipe at the current position.
- gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, m_priming.back().end_pos));
- // Wipe end point: Wipe direction away from the closer tower edge to the further tower edge.
- gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen,
- WipeTower::xy((std::abs(m_left - m_priming.back().end_pos.x) < std::abs(m_right - m_priming.back().end_pos.x)) ? m_right : m_left,
- m_priming.back().end_pos.y)));*/
-
- return gcode;
-}
-
-std::string WipeTowerIntegration::tool_change(GCode &gcodegen, int extruder_id, bool finish_layer)
-{
- std::string gcode;
- assert(m_layer_idx >= 0);
- if (! m_brim_done || gcodegen.writer().need_toolchange(extruder_id) || finish_layer) {
- if (m_layer_idx < (int)m_tool_changes.size()) {
- if (! (size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size()))
- throw std::runtime_error("Wipe tower generation failed, possibly due to empty first layer.");
-
-
- // Calculate where the wipe tower layer will be printed. -1 means that print z will not change,
- // resulting in a wipe tower with sparse layers.
- double wipe_tower_z = -1;
- bool ignore_sparse = false;
- if (gcodegen.config().wipe_tower_no_sparse_layers.value) {
- wipe_tower_z = m_last_wipe_tower_print_z;
- ignore_sparse = (m_brim_done && m_tool_changes[m_layer_idx].size() == 1 && m_tool_changes[m_layer_idx].front().initial_tool == m_tool_changes[m_layer_idx].front().new_tool);
- if (m_tool_change_idx == 0 && ! ignore_sparse)
- wipe_tower_z = m_last_wipe_tower_print_z + m_tool_changes[m_layer_idx].front().layer_height;
- }
-
- if (! ignore_sparse) {
- gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][m_tool_change_idx++], extruder_id, wipe_tower_z);
- m_last_wipe_tower_print_z = wipe_tower_z;
- }
- }
- m_brim_done = true;
- }
- return gcode;
-}
-
-// Print is finished. Now it remains to unload the filament safely with ramming over the wipe tower.
-std::string WipeTowerIntegration::finalize(GCode &gcodegen)
-{
- std::string gcode;
- if (std::abs(gcodegen.writer().get_position()(2) - m_final_purge.print_z) > EPSILON)
- gcode += gcodegen.change_layer(m_final_purge.print_z);
- gcode += append_tcr(gcodegen, m_final_purge, -1);
- return gcode;
-}
+#if ENABLE_GCODE_VIEWER
+ const std::vector ColorPrintColors::Colors = { "#C0392B", "#E67E22", "#F1C40F", "#27AE60", "#1ABC9C", "#2980B9", "#9B59B6" };
+#endif // ENABLE_GCODE_VIEWER
#define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_writer.extruder()->id())
// Collect pairs of object_layer + support_layer sorted by print_z.
// object_layer & support_layer are considered to be on the same print_z, if they are not further than EPSILON.
-std::vector GCode::collect_layers_to_print(const PrintObject &object)
+std::vector GCode::collect_layers_to_print(const PrintObject& object)
{
std::vector layers_to_print;
layers_to_print.reserve(object.layers().size() + object.support_layers().size());
- // Calculate a minimum support layer height as a minimum over all extruders, but not smaller than 10um.
- // This is the same logic as in support generator.
- //FIXME should we use the printing extruders instead?
- double gap_over_supports = object.config().support_material_contact_distance;
- // FIXME should we test object.config().support_material_synchronize_layers ? Currently the support layers are synchronized with object layers iff soluble supports.
- assert(! object.config().support_material || gap_over_supports != 0. || object.config().support_material_synchronize_layers);
+ // Calculate a minimum support layer height as a minimum over all extruders, but not smaller than 10um.
+ // This is the same logic as in support generator.
+ //FIXME should we use the printing extruders instead?
+ double gap_over_supports = object.config().support_material_contact_distance;
+ // FIXME should we test object.config().support_material_synchronize_layers ? Currently the support layers are synchronized with object layers iff soluble supports.
+ assert(!object.config().support_material || gap_over_supports != 0. || object.config().support_material_synchronize_layers);
if (gap_over_supports != 0.) {
gap_over_supports = std::max(0., gap_over_supports);
- // Not a soluble support,
- double support_layer_height_min = 1000000.;
- for (auto lh : object.print()->config().min_layer_height.values)
- support_layer_height_min = std::min(support_layer_height_min, std::max(0.01, lh));
- gap_over_supports += support_layer_height_min;
+ // Not a soluble support,
+ double support_layer_height_min = 1000000.;
+ for (auto lh : object.print()->config().min_layer_height.values)
+ support_layer_height_min = std::min(support_layer_height_min, std::max(0.01, lh));
+ gap_over_supports += support_layer_height_min;
}
// Pair the object layers with the support layers by z.
- size_t idx_object_layer = 0;
+ size_t idx_object_layer = 0;
size_t idx_support_layer = 0;
const LayerToPrint* last_extrusion_layer = nullptr;
while (idx_object_layer < object.layers().size() || idx_support_layer < object.support_layers().size()) {
LayerToPrint layer_to_print;
- layer_to_print.object_layer = (idx_object_layer < object.layers().size()) ? object.layers()[idx_object_layer ++] : nullptr;
- layer_to_print.support_layer = (idx_support_layer < object.support_layers().size()) ? object.support_layers()[idx_support_layer ++] : nullptr;
+ layer_to_print.object_layer = (idx_object_layer < object.layers().size()) ? object.layers()[idx_object_layer++] : nullptr;
+ layer_to_print.support_layer = (idx_support_layer < object.support_layers().size()) ? object.support_layers()[idx_support_layer++] : nullptr;
if (layer_to_print.object_layer && layer_to_print.support_layer) {
if (layer_to_print.object_layer->print_z < layer_to_print.support_layer->print_z - EPSILON) {
layer_to_print.support_layer = nullptr;
- -- idx_support_layer;
- } else if (layer_to_print.support_layer->print_z < layer_to_print.object_layer->print_z - EPSILON) {
+ --idx_support_layer;
+ }
+ else if (layer_to_print.support_layer->print_z < layer_to_print.object_layer->print_z - EPSILON) {
layer_to_print.object_layer = nullptr;
- -- idx_object_layer;
+ --idx_object_layer;
}
}
layers_to_print.emplace_back(layer_to_print);
bool has_extrusions = (layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions())
- || (layer_to_print.support_layer && layer_to_print.support_layer->has_extrusions());
+ || (layer_to_print.support_layer && layer_to_print.support_layer->has_extrusions());
// Check that there are extrusions on the very first layer.
if (layers_to_print.size() == 1u) {
- if (! has_extrusions)
+ if (!has_extrusions)
throw std::runtime_error(_(L("There is an object with no extrusions on the first layer.")));
}
// In case there are extrusions on this layer, check there is a layer to lay it on.
if ((layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions())
- // Allow empty support layers, as the support generator may produce no extrusions for non-empty support regions.
- || (layer_to_print.support_layer /* && layer_to_print.support_layer->has_extrusions() */)) {
+ // Allow empty support layers, as the support generator may produce no extrusions for non-empty support regions.
+ || (layer_to_print.support_layer /* && layer_to_print.support_layer->has_extrusions() */)) {
double support_contact_z = (last_extrusion_layer && last_extrusion_layer->support_layer)
- ? gap_over_supports
- : 0.;
+ ? gap_over_supports
+ : 0.;
double maximal_print_z = (last_extrusion_layer ? last_extrusion_layer->print_z() : 0.)
- + layer_to_print.layer()->height
- + support_contact_z;
+ + layer_to_print.layer()->height
+ + support_contact_z;
// Negative support_contact_z is not taken into account, it can result in false positives in cases
// where previous layer has object extrusions too (https://github.com/prusa3d/PrusaSlicer/issues/2752)
@@ -644,8 +649,8 @@ std::vector GCode::collect_layers_to_print(const PrintObjec
_(L("Empty layers detected, the output would not be printable.")) + "\n\n" +
_(L("Object name")) + ": " + object.model_object()->name + "\n" + _(L("Print z")) + ": " +
std::to_string(layers_to_print.back().print_z()) + "\n\n" + _(L("This is "
- "usually caused by negligibly small extrusions or by a faulty model. Try to repair "
- "the model or change its orientation on the bed.")));
+ "usually caused by negligibly small extrusions or by a faulty model. Try to repair "
+ "the model or change its orientation on the bed.")));
}
// Remember last layer with extrusions.
@@ -660,7 +665,7 @@ std::vector GCode::collect_layers_to_print(const PrintObjec
// Prepare for non-sequential printing of multiple objects: Support resp. object layers with nearly identical print_z
// will be printed for all objects at once.
// Return a list of items.
-std::vector>> GCode::collect_layers_to_print(const Print &print)
+std::vector>> GCode::collect_layers_to_print(const Print& print)
{
struct OrderingItem {
coordf_t print_z;
@@ -675,15 +680,15 @@ std::vector>> GCode::collec
OrderingItem ordering_item;
ordering_item.object_idx = i;
ordering.reserve(ordering.size() + per_object[i].size());
- const LayerToPrint &front = per_object[i].front();
- for (const LayerToPrint <p : per_object[i]) {
- ordering_item.print_z = ltp.print_z();
+ const LayerToPrint& front = per_object[i].front();
+ for (const LayerToPrint& ltp : per_object[i]) {
+ ordering_item.print_z = ltp.print_z();
ordering_item.layer_idx = <p - &front;
ordering.emplace_back(ordering_item);
}
}
- std::sort(ordering.begin(), ordering.end(), [](const OrderingItem &oi1, const OrderingItem &oi2) { return oi1.print_z < oi2.print_z; });
+ std::sort(ordering.begin(), ordering.end(), [](const OrderingItem& oi1, const OrderingItem& oi2) { return oi1.print_z < oi2.print_z; });
std::vector>> layers_to_print;
@@ -692,14 +697,14 @@ std::vector>> GCode::collec
// Find the last layer with roughly the same print_z.
size_t j = i + 1;
coordf_t zmax = ordering[i].print_z + EPSILON;
- for (; j < ordering.size() && ordering[j].print_z <= zmax; ++ j) ;
+ for (; j < ordering.size() && ordering[j].print_z <= zmax; ++j);
// Merge into layers_to_print.
std::pair> merged;
// Assign an average print_z to the set of layers with nearly equal print_z.
- merged.first = 0.5 * (ordering[i].print_z + ordering[j-1].print_z);
+ merged.first = 0.5 * (ordering[i].print_z + ordering[j - 1].print_z);
merged.second.assign(print.objects().size(), LayerToPrint());
for (; i < j; ++i) {
- const OrderingItem &oi = ordering[i];
+ const OrderingItem& oi = ordering[i];
assert(merged.second[oi.object_idx].layer() == nullptr);
merged.second[oi.object_idx] = std::move(per_object[oi.object_idx][oi.layer_idx]);
}
@@ -709,7 +714,22 @@ std::vector>> GCode::collec
return layers_to_print;
}
+#if ENABLE_GCODE_VIEWER
+// free functions called by GCode::do_export()
+namespace DoExport {
+ static void update_print_estimated_times_stats(const GCodeProcessor& processor, PrintStatistics& print_statistics)
+ {
+ const GCodeProcessor::Result& result = processor.get_result();
+ print_statistics.estimated_normal_print_time = get_time_dhm(result.time_statistics.modes[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Normal)].time);
+ print_statistics.estimated_silent_print_time = processor.is_stealth_time_estimator_enabled() ?
+ get_time_dhm(result.time_statistics.modes[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].time) : "N/A";
+ }
+} // namespace DoExport
+
+void GCode::do_export(Print* print, const char* path, GCodeProcessor::Result* result, ThumbnailsGeneratorCallback thumbnail_cb)
+#else
void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_data, ThumbnailsGeneratorCallback thumbnail_cb)
+#endif // ENABLE_GCODE_VIEWER
{
PROFILE_CLEAR();
@@ -731,7 +751,9 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_
if (file == nullptr)
throw std::runtime_error(std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n");
+#if !ENABLE_GCODE_VIEWER
m_enable_analyzer = preview_data != nullptr;
+#endif // !ENABLE_GCODE_VIEWER
try {
m_placeholder_parser_failed_templates.clear();
@@ -764,6 +786,12 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_
throw std::runtime_error(msg);
}
+#if ENABLE_GCODE_VIEWER
+ m_processor.process_file(path_tmp);
+ DoExport::update_print_estimated_times_stats(m_processor, print->m_print_statistics);
+ if (result != nullptr)
+ *result = std::move(m_processor.extract_result());
+#else
GCodeTimeEstimator::PostProcessData normal_data = m_normal_time_estimator.get_post_process_data();
GCodeTimeEstimator::PostProcessData silent_data = m_silent_time_estimator.get_post_process_data();
@@ -772,8 +800,7 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_
BOOST_LOG_TRIVIAL(debug) << "Time estimator post processing" << log_memory_info();
GCodeTimeEstimator::post_process(path_tmp, 60.0f, remaining_times_enabled ? &normal_data : nullptr, (remaining_times_enabled && m_silent_time_estimator_enabled) ? &silent_data : nullptr);
- if (remaining_times_enabled)
- {
+ if (remaining_times_enabled) {
m_normal_time_estimator.reset();
if (m_silent_time_estimator_enabled)
m_silent_time_estimator.reset();
@@ -785,6 +812,7 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_
m_analyzer.calc_gcode_preview_data(*preview_data, [print]() { print->throw_if_canceled(); });
m_analyzer.reset();
}
+#endif // ENABLE_GCODE_VIEWER
if (rename_file(path_tmp, path))
throw std::runtime_error(
@@ -801,7 +829,8 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_
// free functions called by GCode::_do_export()
namespace DoExport {
- static void init_time_estimators(const PrintConfig &config, GCodeTimeEstimator &normal_time_estimator, GCodeTimeEstimator &silent_time_estimator, bool &silent_time_estimator_enabled)
+#if !ENABLE_GCODE_VIEWER
+ static void init_time_estimators(const PrintConfig &config, GCodeTimeEstimator &normal_time_estimator, GCodeTimeEstimator &silent_time_estimator, bool &silent_time_estimator_enabled)
{
// resets time estimators
normal_time_estimator.reset();
@@ -873,8 +902,18 @@ namespace DoExport {
normal_time_estimator.set_filament_unload_times(config.filament_unload_time.values);
}
}
+#endif // !ENABLE_GCODE_VIEWER
- static void init_gcode_analyzer(const PrintConfig &config, GCodeAnalyzer &analyzer)
+#if ENABLE_GCODE_VIEWER
+ static void init_gcode_processor(const PrintConfig& config, GCodeProcessor& processor, bool& silent_time_estimator_enabled)
+ {
+ silent_time_estimator_enabled = (config.gcode_flavor == gcfMarlin) && config.silent_mode;
+ processor.reset();
+ processor.apply_config(config);
+ processor.enable_stealth_time_estimator(silent_time_estimator_enabled);
+ }
+#else
+ static void init_gcode_analyzer(const PrintConfig &config, GCodeAnalyzer &analyzer)
{
// resets analyzer
analyzer.reset();
@@ -898,7 +937,8 @@ namespace DoExport {
// tell analyzer about the gcode flavor
analyzer.set_gcode_flavor(config.gcode_flavor);
- }
+ }
+#endif // ENABLE_GCODE_VIEWER
static double autospeed_volumetric_limit(const Print &print)
{
@@ -1020,24 +1060,28 @@ namespace DoExport {
}
// Fill in print_statistics and return formatted string containing filament statistics to be inserted into G-code comment section.
- static std::string update_print_stats_and_format_filament_stats(
- const GCodeTimeEstimator &normal_time_estimator,
+ static std::string update_print_stats_and_format_filament_stats(
+#if !ENABLE_GCODE_VIEWER
+ const GCodeTimeEstimator &normal_time_estimator,
const GCodeTimeEstimator &silent_time_estimator,
const bool silent_time_estimator_enabled,
- const bool has_wipe_tower,
+#endif // !ENABLE_GCODE_VIEWER
+ const bool has_wipe_tower,
const WipeTowerData &wipe_tower_data,
const std::vector &extruders,
PrintStatistics &print_statistics)
- {
+ {
std::string filament_stats_string_out;
print_statistics.clear();
- print_statistics.estimated_normal_print_time = normal_time_estimator.get_time_dhm/*s*/();
- print_statistics.estimated_silent_print_time = silent_time_estimator_enabled ? silent_time_estimator.get_time_dhm/*s*/() : "N/A";
- print_statistics.estimated_normal_custom_gcode_print_times = normal_time_estimator.get_custom_gcode_times_dhm(true);
- if (silent_time_estimator_enabled)
- print_statistics.estimated_silent_custom_gcode_print_times = silent_time_estimator.get_custom_gcode_times_dhm(true);
- print_statistics.total_toolchanges = std::max(0, wipe_tower_data.number_of_toolchanges);
+#if !ENABLE_GCODE_VIEWER
+ print_statistics.estimated_normal_print_time = normal_time_estimator.get_time_dhm/*s*/();
+ print_statistics.estimated_silent_print_time = silent_time_estimator_enabled ? silent_time_estimator.get_time_dhm/*s*/() : "N/A";
+ print_statistics.estimated_normal_custom_gcode_print_times = normal_time_estimator.get_custom_gcode_times_dhm(true);
+ if (silent_time_estimator_enabled)
+ print_statistics.estimated_silent_custom_gcode_print_times = silent_time_estimator.get_custom_gcode_times_dhm(true);
+#endif // !ENABLE_GCODE_VIEWER
+ print_statistics.total_toolchanges = std::max(0, wipe_tower_data.number_of_toolchanges);
if (! extruders.empty()) {
std::pair out_filament_used_mm ("; filament used [mm] = ", 0);
std::pair out_filament_used_cm3("; filament used [cm3] = ", 0);
@@ -1127,15 +1171,29 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
{
PROFILE_FUNC();
- DoExport::init_time_estimators(print.config(),
- // modifies the following:
- m_normal_time_estimator, m_silent_time_estimator, m_silent_time_estimator_enabled);
+#if ENABLE_GCODE_VIEWER
+ // modifies m_silent_time_estimator_enabled
+ DoExport::init_gcode_processor(print.config(), m_processor, m_silent_time_estimator_enabled);
+#else
+ DoExport::init_time_estimators(print.config(),
+ // modifies the following:
+ m_normal_time_estimator, m_silent_time_estimator, m_silent_time_estimator_enabled);
DoExport::init_gcode_analyzer(print.config(), m_analyzer);
+#endif // ENABLE_GCODE_VIEWER
// resets analyzer's tracking data
+#if ENABLE_GCODE_VIEWER
+ m_last_height = 0.0f;
+ m_last_layer_z = 0.0f;
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+ m_last_mm3_per_mm = 0.0;
+ m_last_width = 0.0f;
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
+#else
m_last_mm3_per_mm = GCodeAnalyzer::Default_mm3_per_mm;
m_last_width = GCodeAnalyzer::Default_Width;
m_last_height = GCodeAnalyzer::Default_Height;
+#endif // ENABLE_GCODE_VIEWER
// How many times will be change_layer() called?
// change_layer() in turn increments the progress bar status.
@@ -1226,12 +1284,16 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
print.throw_if_canceled();
// adds tags for time estimators
+#if ENABLE_GCODE_VIEWER
if (print.config().remaining_times.value)
- {
+ _writeln(file, GCodeProcessor::First_Line_M73_Placeholder_Tag);
+#else
+ if (print.config().remaining_times.value) {
_writeln(file, GCodeTimeEstimator::Normal_First_M73_Output_Placeholder_Tag);
if (m_silent_time_estimator_enabled)
_writeln(file, GCodeTimeEstimator::Silent_First_M73_Output_Placeholder_Tag);
}
+#endif // ENABLE_GCODE_VIEWER
// Prepare the helper object for replacing placeholders in custom G-code and output filename.
m_placeholder_parser = print.placeholder_parser();
@@ -1336,9 +1398,14 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
// Set extruder(s) temperature before and after start G-code.
this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, false);
+#if ENABLE_GCODE_VIEWER
+ // adds tag for processor
+ _write_format(file, ";%s%s\n", GCodeProcessor::Extrusion_Role_Tag.c_str(), ExtrusionEntity::role_to_string(erCustom).c_str());
+#else
if (m_enable_analyzer)
// adds tag for analyzer
_write_format(file, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erCustom);
+#endif // ENABLE_GCODE_VIEWER
// Write the custom start G-code
_writeln(file, start_gcode);
@@ -1489,9 +1556,14 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
_write(file, this->retract());
_write(file, m_writer.set_fan(false));
+#if ENABLE_GCODE_VIEWER
+ // adds tag for processor
+ _write_format(file, ";%s%s\n", GCodeProcessor::Extrusion_Role_Tag.c_str(), ExtrusionEntity::role_to_string(erCustom).c_str());
+#else
if (m_enable_analyzer)
// adds tag for analyzer
_write_format(file, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erCustom);
+#endif // ENABLE_GCODE_VIEWER
// Process filament-specific gcode in extruder order.
{
@@ -1516,25 +1588,33 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
_write(file, m_writer.postamble());
// adds tags for time estimators
+#if ENABLE_GCODE_VIEWER
if (print.config().remaining_times.value)
- {
+ _writeln(file, GCodeProcessor::Last_Line_M73_Placeholder_Tag);
+#else
+ if (print.config().remaining_times.value) {
_writeln(file, GCodeTimeEstimator::Normal_Last_M73_Output_Placeholder_Tag);
if (m_silent_time_estimator_enabled)
_writeln(file, GCodeTimeEstimator::Silent_Last_M73_Output_Placeholder_Tag);
}
+#endif // ENABLE_GCODE_VIEWER
print.throw_if_canceled();
// calculates estimated printing time
+#if !ENABLE_GCODE_VIEWER
m_normal_time_estimator.calculate_time(false);
if (m_silent_time_estimator_enabled)
m_silent_time_estimator.calculate_time(false);
+#endif // !ENABLE_GCODE_VIEWER
// Get filament stats.
_write(file, DoExport::update_print_stats_and_format_filament_stats(
// Const inputs
- m_normal_time_estimator, m_silent_time_estimator, m_silent_time_estimator_enabled,
- has_wipe_tower, print.wipe_tower_data(),
+#if !ENABLE_GCODE_VIEWER
+ m_normal_time_estimator, m_silent_time_estimator, m_silent_time_estimator_enabled,
+#endif // !ENABLE_GCODE_VIEWER
+ has_wipe_tower, print.wipe_tower_data(),
m_writer.extruders(),
// Modifies
print.m_print_statistics));
@@ -1543,9 +1623,13 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
_write_format(file, "; total filament cost = %.1lf\n", print.m_print_statistics.total_cost);
if (print.m_print_statistics.total_toolchanges > 0)
_write_format(file, "; total toolchanges = %i\n", print.m_print_statistics.total_toolchanges);
+#if ENABLE_GCODE_VIEWER
+ _writeln(file, GCodeProcessor::Estimated_Printing_Time_Placeholder_Tag);
+#else
_write_format(file, "; estimated printing time (normal mode) = %s\n", m_normal_time_estimator.get_time_dhms().c_str());
if (m_silent_time_estimator_enabled)
_write_format(file, "; estimated printing time (silent mode) = %s\n", m_silent_time_estimator.get_time_dhms().c_str());
+#endif // ENABLE_GCODE_VIEWER
// Append full config.
_write(file, "\n");
@@ -1815,10 +1899,15 @@ namespace ProcessLayer
{
assert(m600_extruder_before_layer >= 0);
// Color Change or Tool Change as Color Change.
- // add tag for analyzer
- gcode += "; " + GCodeAnalyzer::Color_Change_Tag + ",T" + std::to_string(m600_extruder_before_layer) + "\n";
- // add tag for time estimator
- gcode += "; " + GCodeTimeEstimator::Color_Change_Tag + "\n";
+#if ENABLE_GCODE_VIEWER
+ // add tag for processor
+ gcode += "; " + GCodeProcessor::Color_Change_Tag + ",T" + std::to_string(m600_extruder_before_layer) + "\n";
+#else
+ // add tag for analyzer
+ gcode += "; " + GCodeAnalyzer::Color_Change_Tag + ",T" + std::to_string(m600_extruder_before_layer) + "\n";
+ // add tag for time estimator
+ gcode += "; " + GCodeTimeEstimator::Color_Change_Tag + "\n";
+#endif // ENABLE_GCODE_VIEWER
if (!single_extruder_printer && m600_extruder_before_layer >= 0 && first_extruder_id != (unsigned)m600_extruder_before_layer
// && !MMU1
@@ -1837,20 +1926,32 @@ namespace ProcessLayer
{
if (gcode_type == CustomGCode::PausePrint) // Pause print
{
- // add tag for analyzer
- gcode += "; " + GCodeAnalyzer::Pause_Print_Tag + "\n";
- //! FIXME_in_fw show message during print pause
+#if ENABLE_GCODE_VIEWER
+ // add tag for processor
+ gcode += "; " + GCodeProcessor::Pause_Print_Tag + "\n";
+#else
+ // add tag for analyzer
+ gcode += "; " + GCodeAnalyzer::Pause_Print_Tag + "\n";
+#endif // ENABLE_GCODE_VIEWER
+ //! FIXME_in_fw show message during print pause
if (!pause_print_msg.empty())
gcode += "M117 " + pause_print_msg + "\n";
- // add tag for time estimator
+#if !ENABLE_GCODE_VIEWER
+ // add tag for time estimator
gcode += "; " + GCodeTimeEstimator::Pause_Print_Tag + "\n";
gcode += config.pause_print_gcode;
- }
+#endif // !ENABLE_GCODE_VIEWER
+ }
else
{
- // add tag for analyzer
- gcode += "; " + GCodeAnalyzer::Custom_Code_Tag + "\n";
- // add tag for time estimator
+#if ENABLE_GCODE_VIEWER
+ // add tag for processor
+ gcode += "; " + GCodeProcessor::Custom_Code_Tag + "\n";
+#else
+ // add tag for analyzer
+ gcode += "; " + GCodeAnalyzer::Custom_Code_Tag + "\n";
+#endif // ENABLE_GCODE_VIEWER
+ // add tag for time estimator
//gcode += "; " + GCodeTimeEstimator::Custom_Code_Tag + "\n";
if (gcode_type == CustomGCode::Template) // Template Cistom Gcode
gcode += config.template_custom_gcode;
@@ -1993,6 +2094,22 @@ void GCode::process_layer(
std::string gcode;
+#if ENABLE_GCODE_VIEWER
+ // add tag for processor
+ gcode += "; " + GCodeProcessor::Layer_Change_Tag + "\n";
+ // export layer z
+ char buf[64];
+ sprintf(buf, ";Z:%g\n", print_z);
+ gcode += buf;
+ // export layer height
+ float height = first_layer ? static_cast(print_z) : static_cast(print_z) - m_last_layer_z;
+ sprintf(buf, ";%s%g\n", GCodeProcessor::Height_Tag.c_str(), height);
+ gcode += buf;
+ // update caches
+ m_last_layer_z = static_cast(print_z);
+ m_last_height = height;
+#endif // ENABLE_GCODE_VIEWER
+
// Set new layer - this will change Z and force a retraction if retract_layer_change is enabled.
if (! print.config().before_layer_gcode.value.empty()) {
DynamicConfig config;
@@ -2206,9 +2323,15 @@ void GCode::process_layer(
m_wipe_tower->tool_change(*this, extruder_id, extruder_id == layer_tools.extruders.back()) :
this->set_extruder(extruder_id, print_z);
+#if ENABLE_GCODE_VIEWER
+ // let analyzer tag generator aware of a role type change
+ if (layer_tools.has_wipe_tower && m_wipe_tower)
+ m_last_processor_extrusion_role = erWipeTower;
+#else
// let analyzer tag generator aware of a role type change
if (m_enable_analyzer && layer_tools.has_wipe_tower && m_wipe_tower)
m_last_analyzer_extrusion_role = erWipeTower;
+#endif // ENABLE_GCODE_VIEWER
if (auto loops_it = skirt_loops_per_extruder.find(extruder_id); loops_it != skirt_loops_per_extruder.end()) {
const std::pair loops = loops_it->second;
@@ -2312,11 +2435,13 @@ void GCode::process_layer(
if (m_cooling_buffer)
gcode = m_cooling_buffer->process_layer(gcode, layer.id());
+#if !ENABLE_GCODE_VIEWER
// add tag for analyzer
if (gcode.find(GCodeAnalyzer::Pause_Print_Tag) != gcode.npos)
gcode += "\n; " + GCodeAnalyzer::End_Pause_Print_Or_Custom_Code_Tag + "\n";
else if (gcode.find(GCodeAnalyzer::Custom_Code_Tag) != gcode.npos)
gcode += "\n; " + GCodeAnalyzer::End_Pause_Print_Or_Custom_Code_Tag + "\n";
+#endif // !ENABLE_GCODE_VIEWER
#ifdef HAS_PRESSURE_EQUALIZER
// Apply pressure equalization if enabled;
@@ -2327,12 +2452,14 @@ void GCode::process_layer(
#endif /* HAS_PRESSURE_EQUALIZER */
_write(file, gcode);
- BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z <<
+#if !ENABLE_GCODE_VIEWER
+ BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z <<
", time estimator memory: " <<
format_memsize_MB(m_normal_time_estimator.memory_used() + (m_silent_time_estimator_enabled ? m_silent_time_estimator.memory_used() : 0)) <<
- ", analyzer memory: " <<
+ ", analyzer memory: " <<
format_memsize_MB(m_analyzer.memory_used()) <<
- log_memory_info();
+ log_memory_info();
+#endif // !ENABLE_GCODE_VIEWER
}
void GCode::apply_print_config(const PrintConfig &print_config)
@@ -2972,15 +3099,21 @@ std::string GCode::extrude_support(const ExtrusionEntityCollection &support_fill
void GCode::_write(FILE* file, const char *what)
{
if (what != nullptr) {
+#if ENABLE_GCODE_VIEWER
+ const char* gcode = what;
+#else
// apply analyzer, if enabled
const char* gcode = m_enable_analyzer ? m_analyzer.process_gcode(what).c_str() : what;
+#endif // !ENABLE_GCODE_VIEWER
// writes string to file
fwrite(gcode, 1, ::strlen(gcode), file);
+#if !ENABLE_GCODE_VIEWER
// updates time estimator and gcode lines vector
m_normal_time_estimator.add_gcode_block(gcode);
if (m_silent_time_estimator_enabled)
m_silent_time_estimator.add_gcode_block(gcode);
+#endif // !ENABLE_GCODE_VIEWER
}
}
@@ -3119,42 +3252,71 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
}
}
- // adds analyzer tags and updates analyzer's tracking data
- if (m_enable_analyzer)
- {
+ // adds processor tags and updates processor tracking data
+#if ENABLE_GCODE_VIEWER
+ // PrusaMultiMaterial::Writer may generate GCodeProcessor::Height_Tag lines without updating m_last_height
+ // so, if the last role was erWipeTower we force export of GCodeProcessor::Height_Tag lines
+ bool last_was_wipe_tower = (m_last_processor_extrusion_role == erWipeTower);
+#else
+ if (m_enable_analyzer) {
// PrusaMultiMaterial::Writer may generate GCodeAnalyzer::Height_Tag and GCodeAnalyzer::Width_Tag lines without updating m_last_height and m_last_width
// so, if the last role was erWipeTower we force export of GCodeAnalyzer::Height_Tag and GCodeAnalyzer::Width_Tag lines
bool last_was_wipe_tower = (m_last_analyzer_extrusion_role == erWipeTower);
+#endif // ENABLE_GCODE_VIEWER
char buf[64];
- if (path.role() != m_last_analyzer_extrusion_role)
- {
+#if ENABLE_GCODE_VIEWER
+ if (path.role() != m_last_processor_extrusion_role) {
+ m_last_processor_extrusion_role = path.role();
+ sprintf(buf, ";%s%s\n", GCodeProcessor::Extrusion_Role_Tag.c_str(), ExtrusionEntity::role_to_string(m_last_processor_extrusion_role).c_str());
+ gcode += buf;
+ }
+
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+ if (last_was_wipe_tower || (m_last_mm3_per_mm != path.mm3_per_mm)) {
+ m_last_mm3_per_mm = path.mm3_per_mm;
+ sprintf(buf, ";%s%f\n", GCodeProcessor::Mm3_Per_Mm_Tag.c_str(), m_last_mm3_per_mm);
+ gcode += buf;
+ }
+
+ if (last_was_wipe_tower || m_last_width != path.width) {
+ m_last_width = path.width;
+ sprintf(buf, ";%s%g\n", GCodeProcessor::Width_Tag.c_str(), m_last_width);
+ gcode += buf;
+ }
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
+
+ if (last_was_wipe_tower || std::abs(m_last_height - path.height) > EPSILON) {
+ m_last_height = path.height;
+ sprintf(buf, ";%s%g\n", GCodeProcessor::Height_Tag.c_str(), m_last_height);
+ gcode += buf;
+ }
+#else
+ if (path.role() != m_last_analyzer_extrusion_role) {
m_last_analyzer_extrusion_role = path.role();
sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), int(m_last_analyzer_extrusion_role));
gcode += buf;
}
- if (last_was_wipe_tower || (m_last_mm3_per_mm != path.mm3_per_mm))
- {
+ if (last_was_wipe_tower || (m_last_mm3_per_mm != path.mm3_per_mm)) {
m_last_mm3_per_mm = path.mm3_per_mm;
sprintf(buf, ";%s%f\n", GCodeAnalyzer::Mm3_Per_Mm_Tag.c_str(), m_last_mm3_per_mm);
gcode += buf;
}
- if (last_was_wipe_tower || (m_last_width != path.width))
- {
+ if (last_was_wipe_tower || m_last_width != path.width) {
m_last_width = path.width;
sprintf(buf, ";%s%f\n", GCodeAnalyzer::Width_Tag.c_str(), m_last_width);
gcode += buf;
}
- if (last_was_wipe_tower || (m_last_height != path.height))
- {
+ if (last_was_wipe_tower || m_last_height != path.height) {
m_last_height = path.height;
sprintf(buf, ";%s%f\n", GCodeAnalyzer::Height_Tag.c_str(), m_last_height);
gcode += buf;
}
}
+#endif // ENABLE_GCODE_VIEWER
std::string comment;
if (m_enable_cooling_markers) {
diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp
index 8d47337832..8bae2ef43d 100644
--- a/src/libslic3r/GCode.hpp
+++ b/src/libslic3r/GCode.hpp
@@ -13,9 +13,13 @@
#include "GCode/SpiralVase.hpp"
#include "GCode/ToolOrdering.hpp"
#include "GCode/WipeTower.hpp"
-#include "GCodeTimeEstimator.hpp"
-#include "EdgeGrid.hpp"
+#if ENABLE_GCODE_VIEWER
+#include "GCode/GCodeProcessor.hpp"
+#else
#include "GCode/Analyzer.hpp"
+#include "GCodeTimeEstimator.hpp"
+#endif // ENABLE_GCODE_VIEWER
+#include "EdgeGrid.hpp"
#include "GCode/ThumbnailData.hpp"
#include
@@ -29,7 +33,9 @@ namespace Slic3r {
// Forward declarations.
class GCode;
+#if !ENABLE_GCODE_VIEWER
class GCodePreviewData;
+#endif // !ENABLE_GCODE_VIEWER
namespace { struct Item; }
struct PrintInstance;
@@ -138,6 +144,15 @@ private:
double m_last_wipe_tower_print_z = 0.f;
};
+#if ENABLE_GCODE_VIEWER
+class ColorPrintColors
+{
+ static const std::vector Colors;
+public:
+ static const std::vector& get() { return Colors; }
+};
+#endif // ENABLE_GCODE_VIEWER
+
class GCode {
public:
GCode() :
@@ -145,21 +160,33 @@ public:
m_enable_loop_clipping(true),
m_enable_cooling_markers(false),
m_enable_extrusion_role_markers(false),
+#if ENABLE_GCODE_VIEWER
+ m_last_processor_extrusion_role(erNone),
+#else
m_enable_analyzer(false),
m_last_analyzer_extrusion_role(erNone),
+#endif // ENABLE_GCODE_VIEWER
m_layer_count(0),
m_layer_index(-1),
m_layer(nullptr),
m_volumetric_speed(0),
m_last_pos_defined(false),
m_last_extrusion_role(erNone),
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+ m_last_mm3_per_mm(0.0),
+ m_last_width(0.0f),
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
+#if !ENABLE_GCODE_VIEWER
m_last_mm3_per_mm(GCodeAnalyzer::Default_mm3_per_mm),
m_last_width(GCodeAnalyzer::Default_Width),
m_last_height(GCodeAnalyzer::Default_Height),
+#endif // !ENABLE_GCODE_VIEWER
m_brim_done(false),
m_second_layer_things_done(false),
+#if !ENABLE_GCODE_VIEWER
m_normal_time_estimator(GCodeTimeEstimator::Normal),
m_silent_time_estimator(GCodeTimeEstimator::Silent),
+#endif // !ENABLE_GCODE_VIEWER
m_silent_time_estimator_enabled(false),
m_last_obj_copy(nullptr, Point(std::numeric_limits::max(), std::numeric_limits::max()))
{}
@@ -167,7 +194,11 @@ public:
// throws std::runtime_exception on error,
// throws CanceledException through print->throw_if_canceled().
+#if ENABLE_GCODE_VIEWER
+ void do_export(Print* print, const char* path, GCodeProcessor::Result* result = nullptr, ThumbnailsGeneratorCallback thumbnail_cb = nullptr);
+#else
void do_export(Print* print, const char* path, GCodePreviewData* preview_data = nullptr, ThumbnailsGeneratorCallback thumbnail_cb = nullptr);
+#endif // ENABLE_GCODE_VIEWER
// Exported for the helper classes (OozePrevention, Wipe) and for the Perl binding for unit tests.
const Vec2d& origin() const { return m_origin; }
@@ -327,11 +358,16 @@ private:
// Markers for the Pressure Equalizer to recognize the extrusion type.
// The Pressure Equalizer removes the markers from the final G-code.
bool m_enable_extrusion_role_markers;
+#if ENABLE_GCODE_VIEWER
+ // Keeps track of the last extrusion role passed to the processor
+ ExtrusionRole m_last_processor_extrusion_role;
+#else
// Enableds the G-code Analyzer.
// Extended markers will be added during G-code generation.
// The G-code Analyzer will remove these comments from the final G-code.
bool m_enable_analyzer;
ExtrusionRole m_last_analyzer_extrusion_role;
+#endif // ENABLE_GCODE_VIEWER
// How many times will change_layer() be called?
// change_layer() will update the progress bar.
unsigned int m_layer_count;
@@ -344,10 +380,20 @@ private:
double m_volumetric_speed;
// Support for the extrusion role markers. Which marker is active?
ExtrusionRole m_last_extrusion_role;
+#if ENABLE_GCODE_VIEWER
+ // Support for G-Code Processor
+ float m_last_height{ 0.0f };
+ float m_last_layer_z{ 0.0f };
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+ double m_last_mm3_per_mm;
+ float m_last_width{ 0.0f };
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
+#else
// Support for G-Code Analyzer
double m_last_mm3_per_mm;
float m_last_width;
float m_last_height;
+#endif // ENABLE_GCODE_VIEWER
Point m_last_pos;
bool m_last_pos_defined;
@@ -368,13 +414,20 @@ private:
// Index of a last object copy extruded.
std::pair m_last_obj_copy;
+#if !ENABLE_GCODE_VIEWER
// Time estimators
GCodeTimeEstimator m_normal_time_estimator;
GCodeTimeEstimator m_silent_time_estimator;
+#endif // !ENABLE_GCODE_VIEWER
bool m_silent_time_estimator_enabled;
+#if ENABLE_GCODE_VIEWER
+ // Processor
+ GCodeProcessor m_processor;
+#else
// Analyzer
GCodeAnalyzer m_analyzer;
+#endif // ENABLE_GCODE_VIEWER
// Write a string into a file.
void _write(FILE* file, const std::string& what) { this->_write(file, what.c_str()); }
diff --git a/src/libslic3r/GCode/Analyzer.cpp b/src/libslic3r/GCode/Analyzer.cpp
index 73c20f1b58..d022b37983 100644
--- a/src/libslic3r/GCode/Analyzer.cpp
+++ b/src/libslic3r/GCode/Analyzer.cpp
@@ -12,6 +12,8 @@
#include "Analyzer.hpp"
#include "PreviewData.hpp"
+#if !ENABLE_GCODE_VIEWER
+
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;
@@ -350,7 +352,7 @@ void GCodeAnalyzer::_processG1(const GCodeReader::GCodeLine& line)
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;
+ type = GCodeMove::Move;
else
type = GCodeMove::Retract;
}
@@ -651,7 +653,7 @@ bool GCodeAnalyzer::_process_tags(const GCodeReader::GCodeLine& line)
return true;
}
- // color change tag
+ // pause print tag
pos = comment.find(Pause_Print_Tag);
if (pos != comment.npos)
{
@@ -659,7 +661,7 @@ bool GCodeAnalyzer::_process_tags(const GCodeReader::GCodeLine& line)
return true;
}
- // color change tag
+ // custom code tag
pos = comment.find(Custom_Code_Tag);
if (pos != comment.npos)
{
@@ -667,7 +669,7 @@ bool GCodeAnalyzer::_process_tags(const GCodeReader::GCodeLine& line)
return true;
}
- // color change tag
+ // end pause print or custom code tag
pos = comment.find(End_Pause_Print_Or_Custom_Code_Tag);
if (pos != comment.npos)
{
@@ -1191,3 +1193,5 @@ size_t GCodeAnalyzer::memory_used() const
}
} // namespace Slic3r
+
+#endif // !ENABLE_GCODE_VIEWER
diff --git a/src/libslic3r/GCode/Analyzer.hpp b/src/libslic3r/GCode/Analyzer.hpp
index 4ac383fea6..37d9072592 100644
--- a/src/libslic3r/GCode/Analyzer.hpp
+++ b/src/libslic3r/GCode/Analyzer.hpp
@@ -1,6 +1,8 @@
#ifndef slic3r_GCode_Analyzer_hpp_
#define slic3r_GCode_Analyzer_hpp_
+#if !ENABLE_GCODE_VIEWER
+
#include "../libslic3r.h"
#include "../PrintConfig.hpp"
#include "../ExtrusionEntity.hpp"
@@ -302,4 +304,6 @@ private:
} // namespace Slic3r
+#endif // !ENABLE_GCODE_VIEWER
+
#endif /* slic3r_GCode_Analyzer_hpp_ */
diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp
new file mode 100644
index 0000000000..54addbd979
--- /dev/null
+++ b/src/libslic3r/GCode/GCodeProcessor.cpp
@@ -0,0 +1,2162 @@
+#include "libslic3r/libslic3r.h"
+#include "libslic3r/Utils.hpp"
+#include "libslic3r/Print.hpp"
+#include "GCodeProcessor.hpp"
+
+#include
+#include
+
+#include
+#include
+
+#if ENABLE_GCODE_VIEWER
+
+#if ENABLE_GCODE_VIEWER_STATISTICS
+#include
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+
+static const float INCHES_TO_MM = 25.4f;
+static const float MMMIN_TO_MMSEC = 1.0f / 60.0f;
+
+static const float DEFAULT_ACCELERATION = 1500.0f; // Prusa Firmware 1_75mm_MK2
+
+namespace Slic3r {
+
+const std::string GCodeProcessor::Extrusion_Role_Tag = "TYPE:";
+const std::string GCodeProcessor::Height_Tag = "HEIGHT:";
+const std::string GCodeProcessor::Layer_Change_Tag = "LAYER_CHANGE";
+const std::string GCodeProcessor::Color_Change_Tag = "COLOR_CHANGE";
+const std::string GCodeProcessor::Pause_Print_Tag = "PAUSE_PRINT";
+const std::string GCodeProcessor::Custom_Code_Tag = "CUSTOM_GCODE";
+
+const std::string GCodeProcessor::First_Line_M73_Placeholder_Tag = "; _GP_FIRST_LINE_M73_PLACEHOLDER";
+const std::string GCodeProcessor::Last_Line_M73_Placeholder_Tag = "; _GP_LAST_LINE_M73_PLACEHOLDER";
+const std::string GCodeProcessor::Estimated_Printing_Time_Placeholder_Tag = "; _GP_ESTIMATED_PRINTING_TIME_PLACEHOLDER";
+
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+const std::string GCodeProcessor::Width_Tag = "WIDTH:";
+const std::string GCodeProcessor::Mm3_Per_Mm_Tag = "MM3_PER_MM:";
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
+
+static bool is_valid_extrusion_role(int value)
+{
+ return (static_cast(erNone) <= value) && (value <= static_cast(erMixed));
+}
+
+static void set_option_value(ConfigOptionFloats& option, size_t id, float value)
+{
+ if (id < option.values.size())
+ option.values[id] = static_cast(value);
+};
+
+static float get_option_value(const ConfigOptionFloats& option, size_t id)
+{
+ return option.values.empty() ? 0.0f :
+ ((id < option.values.size()) ? static_cast(option.values[id]) : static_cast(option.values.back()));
+}
+
+static float estimated_acceleration_distance(float initial_rate, float target_rate, float acceleration)
+{
+ return (acceleration == 0.0f) ? 0.0f : (sqr(target_rate) - sqr(initial_rate)) / (2.0f * acceleration);
+}
+
+static float intersection_distance(float initial_rate, float final_rate, float acceleration, float distance)
+{
+ return (acceleration == 0.0f) ? 0.0f : (2.0f * acceleration * distance - sqr(initial_rate) + sqr(final_rate)) / (4.0f * acceleration);
+}
+
+static float speed_from_distance(float initial_feedrate, float distance, float acceleration)
+{
+ // to avoid invalid negative numbers due to numerical errors
+ float value = std::max(0.0f, sqr(initial_feedrate) + 2.0f * acceleration * distance);
+ return ::sqrt(value);
+}
+
+// Calculates the maximum allowable speed at this point when you must be able to reach target_velocity using the
+// acceleration within the allotted distance.
+static float max_allowable_speed(float acceleration, float target_velocity, float distance)
+{
+ // to avoid invalid negative numbers due to numerical errors
+ float value = std::max(0.0f, sqr(target_velocity) - 2.0f * acceleration * distance);
+ return std::sqrt(value);
+}
+
+static float acceleration_time_from_distance(float initial_feedrate, float distance, float acceleration)
+{
+ return (acceleration != 0.0f) ? (speed_from_distance(initial_feedrate, distance, acceleration) - initial_feedrate) / acceleration : 0.0f;
+}
+
+void GCodeProcessor::CachedPosition::reset()
+{
+ std::fill(position.begin(), position.end(), FLT_MAX);
+ feedrate = FLT_MAX;
+}
+
+void GCodeProcessor::CpColor::reset()
+{
+ counter = 0;
+ current = 0;
+}
+
+float GCodeProcessor::Trapezoid::acceleration_time(float entry_feedrate, float acceleration) const
+{
+ return acceleration_time_from_distance(entry_feedrate, accelerate_until, acceleration);
+}
+
+float GCodeProcessor::Trapezoid::cruise_time() const
+{
+ return (cruise_feedrate != 0.0f) ? cruise_distance() / cruise_feedrate : 0.0f;
+}
+
+float GCodeProcessor::Trapezoid::deceleration_time(float distance, float acceleration) const
+{
+ return acceleration_time_from_distance(cruise_feedrate, (distance - decelerate_after), -acceleration);
+}
+
+float GCodeProcessor::Trapezoid::cruise_distance() const
+{
+ return decelerate_after - accelerate_until;
+}
+
+void GCodeProcessor::TimeBlock::calculate_trapezoid()
+{
+ trapezoid.cruise_feedrate = feedrate_profile.cruise;
+
+ float accelerate_distance = std::max(0.0f, estimated_acceleration_distance(feedrate_profile.entry, feedrate_profile.cruise, acceleration));
+ float decelerate_distance = std::max(0.0f, estimated_acceleration_distance(feedrate_profile.cruise, feedrate_profile.exit, -acceleration));
+ float cruise_distance = distance - accelerate_distance - decelerate_distance;
+
+ // Not enough space to reach the nominal feedrate.
+ // This means no cruising, and we'll have to use intersection_distance() to calculate when to abort acceleration
+ // and start braking in order to reach the exit_feedrate exactly at the end of this block.
+ if (cruise_distance < 0.0f) {
+ accelerate_distance = std::clamp(intersection_distance(feedrate_profile.entry, feedrate_profile.exit, acceleration, distance), 0.0f, distance);
+ cruise_distance = 0.0f;
+ trapezoid.cruise_feedrate = speed_from_distance(feedrate_profile.entry, accelerate_distance, acceleration);
+ }
+
+ trapezoid.accelerate_until = accelerate_distance;
+ trapezoid.decelerate_after = accelerate_distance + cruise_distance;
+}
+
+float GCodeProcessor::TimeBlock::time() const
+{
+ return trapezoid.acceleration_time(feedrate_profile.entry, acceleration)
+ + trapezoid.cruise_time()
+ + trapezoid.deceleration_time(distance, acceleration);
+}
+
+void GCodeProcessor::TimeMachine::State::reset()
+{
+ feedrate = 0.0f;
+ safe_feedrate = 0.0f;
+ axis_feedrate = { 0.0f, 0.0f, 0.0f, 0.0f };
+ abs_axis_feedrate = { 0.0f, 0.0f, 0.0f, 0.0f };
+}
+
+void GCodeProcessor::TimeMachine::CustomGCodeTime::reset()
+{
+ needed = false;
+ cache = 0.0f;
+ times = std::vector>();
+}
+
+void GCodeProcessor::TimeMachine::reset()
+{
+ enabled = false;
+ acceleration = 0.0f;
+ max_acceleration = 0.0f;
+ extrude_factor_override_percentage = 1.0f;
+ time = 0.0f;
+ curr.reset();
+ prev.reset();
+ gcode_time.reset();
+ blocks = std::vector();
+ g1_times_cache = std::vector();
+ std::fill(moves_time.begin(), moves_time.end(), 0.0f);
+ std::fill(roles_time.begin(), roles_time.end(), 0.0f);
+ layers_time = std::vector();
+}
+
+void GCodeProcessor::TimeMachine::simulate_st_synchronize(float additional_time)
+{
+ if (!enabled)
+ return;
+
+ time += additional_time;
+ gcode_time.cache += additional_time;
+ calculate_time();
+}
+
+static void planner_forward_pass_kernel(GCodeProcessor::TimeBlock& prev, GCodeProcessor::TimeBlock& curr)
+{
+ // If the previous block is an acceleration block, but it is not long enough to complete the
+ // full speed change within the block, we need to adjust the entry speed accordingly. Entry
+ // speeds have already been reset, maximized, and reverse planned by reverse planner.
+ // If nominal length is true, max junction speed is guaranteed to be reached. No need to recheck.
+ if (!prev.flags.nominal_length) {
+ if (prev.feedrate_profile.entry < curr.feedrate_profile.entry) {
+ float entry_speed = std::min(curr.feedrate_profile.entry, max_allowable_speed(-prev.acceleration, prev.feedrate_profile.entry, prev.distance));
+
+ // Check for junction speed change
+ if (curr.feedrate_profile.entry != entry_speed) {
+ curr.feedrate_profile.entry = entry_speed;
+ curr.flags.recalculate = true;
+ }
+ }
+ }
+}
+
+void planner_reverse_pass_kernel(GCodeProcessor::TimeBlock& curr, GCodeProcessor::TimeBlock& next)
+{
+ // If entry speed is already at the maximum entry speed, no need to recheck. Block is cruising.
+ // If not, block in state of acceleration or deceleration. Reset entry speed to maximum and
+ // check for maximum allowable speed reductions to ensure maximum possible planned speed.
+ if (curr.feedrate_profile.entry != curr.max_entry_speed) {
+ // If nominal length true, max junction speed is guaranteed to be reached. Only compute
+ // for max allowable speed if block is decelerating and nominal length is false.
+ if (!curr.flags.nominal_length && curr.max_entry_speed > next.feedrate_profile.entry)
+ curr.feedrate_profile.entry = std::min(curr.max_entry_speed, max_allowable_speed(-curr.acceleration, next.feedrate_profile.entry, curr.distance));
+ else
+ curr.feedrate_profile.entry = curr.max_entry_speed;
+
+ curr.flags.recalculate = true;
+ }
+}
+
+static void recalculate_trapezoids(std::vector& blocks)
+{
+ GCodeProcessor::TimeBlock* curr = nullptr;
+ GCodeProcessor::TimeBlock* next = nullptr;
+
+ for (size_t i = 0; i < blocks.size(); ++i) {
+ GCodeProcessor::TimeBlock& b = blocks[i];
+
+ curr = next;
+ next = &b;
+
+ if (curr != nullptr) {
+ // Recalculate if current block entry or exit junction speed has changed.
+ if (curr->flags.recalculate || next->flags.recalculate) {
+ // NOTE: Entry and exit factors always > 0 by all previous logic operations.
+ GCodeProcessor::TimeBlock block = *curr;
+ block.feedrate_profile.exit = next->feedrate_profile.entry;
+ block.calculate_trapezoid();
+ curr->trapezoid = block.trapezoid;
+ curr->flags.recalculate = false; // Reset current only to ensure next trapezoid is computed
+ }
+ }
+ }
+
+ // Last/newest block in buffer. Always recalculated.
+ if (next != nullptr) {
+ GCodeProcessor::TimeBlock block = *next;
+ block.feedrate_profile.exit = next->safe_feedrate;
+ block.calculate_trapezoid();
+ next->trapezoid = block.trapezoid;
+ next->flags.recalculate = false;
+ }
+}
+
+void GCodeProcessor::TimeMachine::calculate_time(size_t keep_last_n_blocks)
+{
+ if (!enabled || blocks.size() < 2)
+ return;
+
+ assert(keep_last_n_blocks <= blocks.size());
+
+ // forward_pass
+ for (size_t i = 0; i + 1 < blocks.size(); ++i) {
+ planner_forward_pass_kernel(blocks[i], blocks[i + 1]);
+ }
+
+ // reverse_pass
+ for (int i = static_cast(blocks.size()) - 1; i > 0; --i)
+ planner_reverse_pass_kernel(blocks[i - 1], blocks[i]);
+
+ recalculate_trapezoids(blocks);
+
+ size_t n_blocks_process = blocks.size() - keep_last_n_blocks;
+ for (size_t i = 0; i < n_blocks_process; ++i) {
+ const TimeBlock& block = blocks[i];
+ float block_time = block.time();
+ time += block_time;
+ gcode_time.cache += block_time;
+ moves_time[static_cast(block.move_type)] += block_time;
+ roles_time[static_cast(block.role)] += block_time;
+ if (block.layer_id > 0) {
+ if (block.layer_id >= layers_time.size()) {
+ size_t curr_size = layers_time.size();
+ layers_time.resize(block.layer_id);
+ for (size_t i = curr_size; i < layers_time.size(); ++i) {
+ layers_time[i] = 0.0f;
+ }
+ }
+ layers_time[block.layer_id - 1] += block_time;
+ }
+ g1_times_cache.push_back(time);
+ }
+
+ if (keep_last_n_blocks)
+ blocks.erase(blocks.begin(), blocks.begin() + n_blocks_process);
+ else
+ blocks.clear();
+}
+
+void GCodeProcessor::TimeProcessor::reset()
+{
+ extruder_unloaded = true;
+ export_remaining_time_enabled = false;
+ machine_envelope_processing_enabled = false;
+ machine_limits = MachineEnvelopeConfig();
+ filament_load_times = std::vector();
+ filament_unload_times = std::vector();
+ for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
+ machines[i].reset();
+ }
+ machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Normal)].enabled = true;
+}
+
+void GCodeProcessor::TimeProcessor::post_process(const std::string& filename)
+{
+ boost::nowide::ifstream in(filename);
+ if (!in.good())
+ throw std::runtime_error(std::string("Time estimator post process export failed.\nCannot open file for reading.\n"));
+
+ // temporary file to contain modified gcode
+ std::string out_path = filename + ".postprocess";
+ FILE* out = boost::nowide::fopen(out_path.c_str(), "wb");
+ if (out == nullptr)
+ throw std::runtime_error(std::string("Time estimator post process export failed.\nCannot open file for writing.\n"));
+
+ auto time_in_minutes = [](float time_in_seconds) {
+ return int(::roundf(time_in_seconds / 60.0f));
+ };
+
+ auto format_line_M73 = [](const std::string& mask, int percent, int time) {
+ char line_M73[64];
+ sprintf(line_M73, mask.c_str(),
+ std::to_string(percent).c_str(),
+ std::to_string(time).c_str());
+ return std::string(line_M73);
+ };
+
+ GCodeReader parser;
+ std::string gcode_line;
+ size_t g1_lines_counter = 0;
+ // keeps track of last exported pair
+ std::array, static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count)> last_exported;
+ for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
+ last_exported[i] = { 0, time_in_minutes(machines[i].time) };
+ }
+
+ // buffer line to export only when greater than 64K to reduce writing calls
+ std::string export_line;
+
+ // replace placeholder lines with the proper final value
+ auto process_placeholders = [&](const std::string& gcode_line) {
+ // remove trailing '\n'
+ std::string line = gcode_line.substr(0, gcode_line.length() - 1);
+
+ std::string ret;
+
+ if (line == First_Line_M73_Placeholder_Tag || line == Last_Line_M73_Placeholder_Tag) {
+ for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
+ const TimeMachine& machine = machines[i];
+ if (machine.enabled) {
+ ret += format_line_M73(machine.line_m73_mask.c_str(),
+ (line == First_Line_M73_Placeholder_Tag) ? 0 : 100,
+ (line == First_Line_M73_Placeholder_Tag) ? time_in_minutes(machines[i].time) : 0);
+ }
+ }
+ }
+ else if (line == Estimated_Printing_Time_Placeholder_Tag) {
+ for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
+ const TimeMachine& machine = machines[i];
+ if (machine.enabled) {
+ char buf[128];
+ sprintf(buf, "; estimated printing time (%s mode) = %s\n",
+ (static_cast(i) == PrintEstimatedTimeStatistics::ETimeMode::Normal) ? "normal" : "silent",
+ get_time_dhms(machine.time).c_str());
+ ret += buf;
+ }
+ }
+ }
+
+ return std::make_pair(!ret.empty(), ret.empty() ? gcode_line : ret);
+ };
+
+ // check for temporary lines
+ auto is_temporary_decoration = [](const std::string& gcode_line) {
+ // remove trailing '\n'
+ std::string line = gcode_line.substr(0, gcode_line.length() - 1);
+ if (line == "; " + Layer_Change_Tag)
+ return true;
+ else
+ return false;
+ };
+
+ // add lines M73 to exported gcode
+ auto process_line_G1 = [&]() {
+ for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
+ const TimeMachine& machine = machines[i];
+ if (machine.enabled && g1_lines_counter < machine.g1_times_cache.size()) {
+ float elapsed_time = machine.g1_times_cache[g1_lines_counter];
+ std::pair to_export = { int(::roundf(100.0f * elapsed_time / machine.time)),
+ time_in_minutes(machine.time - elapsed_time) };
+ if (last_exported[i] != to_export) {
+ export_line += format_line_M73(machine.line_m73_mask.c_str(),
+ to_export.first, to_export.second);
+ last_exported[i] = to_export;
+ }
+ }
+ }
+ };
+
+ // helper function to write to disk
+ auto write_string = [&](const std::string& str) {
+ fwrite((const void*)export_line.c_str(), 1, export_line.length(), out);
+ if (ferror(out)) {
+ in.close();
+ fclose(out);
+ boost::nowide::remove(out_path.c_str());
+ throw std::runtime_error(std::string("Time estimator post process export failed.\nIs the disk full?\n"));
+ }
+ export_line.clear();
+ };
+
+ while (std::getline(in, gcode_line)) {
+ if (!in.good()) {
+ fclose(out);
+ throw std::runtime_error(std::string("Time estimator post process export failed.\nError while reading from file.\n"));
+ }
+
+ gcode_line += "\n";
+ // replace placeholder lines
+ auto [processed, result] = process_placeholders(gcode_line);
+ gcode_line = result;
+ if (!processed) {
+ // remove temporary lines
+ if (is_temporary_decoration(gcode_line))
+ continue;
+
+ // add lines M73 where needed
+ parser.parse_line(gcode_line,
+ [&](GCodeReader& reader, const GCodeReader::GCodeLine& line) {
+ if (line.cmd_is("G1")) {
+ process_line_G1();
+ ++g1_lines_counter;
+ }
+ });
+ }
+
+ export_line += gcode_line;
+ if (export_line.length() > 65535)
+ write_string(export_line);
+ }
+
+ if (!export_line.empty())
+ write_string(export_line);
+
+ fclose(out);
+ in.close();
+
+ if (rename_file(out_path, filename))
+ throw std::runtime_error(std::string("Failed to rename the output G-code file from ") + out_path + " to " + filename + '\n' +
+ "Is " + out_path + " locked?" + '\n');
+}
+
+const std::vector> GCodeProcessor::Producers = {
+ { EProducer::PrusaSlicer, "PrusaSlicer" },
+ { EProducer::Cura, "Cura_SteamEngine" },
+ { EProducer::Simplify3D, "Simplify3D" },
+ { EProducer::CraftWare, "CraftWare" },
+ { EProducer::ideaMaker, "ideaMaker" }
+};
+
+unsigned int GCodeProcessor::s_result_id = 0;
+
+GCodeProcessor::GCodeProcessor()
+{
+ reset();
+ m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Normal)].line_m73_mask = "M73 P%s R%s\n";
+ m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].line_m73_mask = "M73 Q%s S%s\n";
+}
+
+void GCodeProcessor::apply_config(const PrintConfig& config)
+{
+ m_parser.apply_config(config);
+
+ m_flavor = config.gcode_flavor;
+
+ size_t extruders_count = config.nozzle_diameter.values.size();
+
+ m_extruder_offsets.resize(extruders_count);
+ for (size_t i = 0; i < extruders_count; ++i) {
+ Vec2f offset = config.extruder_offset.get_at(i).cast();
+ m_extruder_offsets[i] = { offset(0), offset(1), 0.0f };
+ }
+
+ m_extruder_colors.resize(extruders_count);
+ for (size_t i = 0; i < extruders_count; ++i) {
+ m_extruder_colors[i] = static_cast(i);
+ }
+
+ m_filament_diameters.resize(config.filament_diameter.values.size());
+ for (size_t i = 0; i < config.filament_diameter.values.size(); ++i) {
+ m_filament_diameters[i] = static_cast(config.filament_diameter.values[i]);
+ }
+
+ m_time_processor.machine_limits = reinterpret_cast(config);
+ // Filament load / unload times are not specific to a firmware flavor. Let anybody use it if they find it useful.
+ // As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they
+ // are considered to be active for the single extruder multi-material printers only.
+ m_time_processor.filament_load_times.resize(config.filament_load_time.values.size());
+ for (size_t i = 0; i < config.filament_load_time.values.size(); ++i) {
+ m_time_processor.filament_load_times[i] = static_cast(config.filament_load_time.values[i]);
+ }
+ m_time_processor.filament_unload_times.resize(config.filament_unload_time.values.size());
+ for (size_t i = 0; i < config.filament_unload_time.values.size(); ++i) {
+ m_time_processor.filament_unload_times[i] = static_cast(config.filament_unload_time.values[i]);
+ }
+
+ for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
+ float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, i);
+ m_time_processor.machines[i].max_acceleration = max_acceleration;
+ m_time_processor.machines[i].acceleration = (max_acceleration > 0.0f) ? max_acceleration : DEFAULT_ACCELERATION;
+ }
+
+ m_time_processor.export_remaining_time_enabled = config.remaining_times.value;
+}
+
+void GCodeProcessor::apply_config(const DynamicPrintConfig& config)
+{
+ m_parser.apply_config(config);
+
+ const ConfigOptionEnum* gcode_flavor = config.option>("gcode_flavor");
+ if (gcode_flavor != nullptr)
+ m_flavor = gcode_flavor->value;
+
+ const ConfigOptionPoints* bed_shape = config.option("bed_shape");
+ if (bed_shape != nullptr)
+ m_result.bed_shape = bed_shape->values;
+
+ const ConfigOptionString* printer_settings_id = config.option("printer_settings_id");
+ if (printer_settings_id != nullptr)
+ m_result.printer_settings_id = printer_settings_id->value;
+
+ const ConfigOptionFloats* filament_diameters = config.option("filament_diameter");
+ if (filament_diameters != nullptr) {
+ for (double diam : filament_diameters->values) {
+ m_filament_diameters.push_back(static_cast(diam));
+ }
+ }
+
+ const ConfigOptionPoints* extruder_offset = config.option("extruder_offset");
+ if (extruder_offset != nullptr) {
+ m_extruder_offsets.resize(extruder_offset->values.size());
+ for (size_t i = 0; i < extruder_offset->values.size(); ++i) {
+ Vec2f offset = extruder_offset->values[i].cast();
+ m_extruder_offsets[i] = { offset(0), offset(1), 0.0f };
+ }
+ }
+
+ // ensure at least one (default) color is defined
+ std::string default_color = "#FF8000";
+ m_result.extruder_colors = std::vector(1, default_color);
+ const ConfigOptionStrings* extruder_colour = config.option("extruder_colour");
+ if (extruder_colour != nullptr) {
+ // takes colors from config
+ m_result.extruder_colors = extruder_colour->values;
+ // try to replace missing values with filament colors
+ const ConfigOptionStrings* filament_colour = config.option("filament_colour");
+ if (filament_colour != nullptr && filament_colour->values.size() == m_result.extruder_colors.size()) {
+ for (size_t i = 0; i < m_result.extruder_colors.size(); ++i) {
+ if (m_result.extruder_colors[i].empty())
+ m_result.extruder_colors[i] = filament_colour->values[i];
+ }
+ }
+ }
+
+ // replace missing values with default
+ for (size_t i = 0; i < m_result.extruder_colors.size(); ++i) {
+ if (m_result.extruder_colors[i].empty())
+ m_result.extruder_colors[i] = default_color;
+ }
+
+ m_extruder_colors.resize(m_result.extruder_colors.size());
+ for (size_t i = 0; i < m_result.extruder_colors.size(); ++i) {
+ m_extruder_colors[i] = static_cast(i);
+ }
+
+ const ConfigOptionFloats* filament_load_time = config.option("filament_load_time");
+ if (filament_load_time != nullptr) {
+ m_time_processor.filament_load_times.resize(filament_load_time->values.size());
+ for (size_t i = 0; i < filament_load_time->values.size(); ++i) {
+ m_time_processor.filament_load_times[i] = static_cast(filament_load_time->values[i]);
+ }
+ }
+
+ const ConfigOptionFloats* filament_unload_time = config.option("filament_unload_time");
+ if (filament_unload_time != nullptr) {
+ m_time_processor.filament_unload_times.resize(filament_unload_time->values.size());
+ for (size_t i = 0; i < filament_unload_time->values.size(); ++i) {
+ m_time_processor.filament_unload_times[i] = static_cast(filament_unload_time->values[i]);
+ }
+ }
+
+ const ConfigOptionFloats* machine_max_acceleration_x = config.option("machine_max_acceleration_x");
+ if (machine_max_acceleration_x != nullptr)
+ m_time_processor.machine_limits.machine_max_acceleration_x.values = machine_max_acceleration_x->values;
+
+ const ConfigOptionFloats* machine_max_acceleration_y = config.option("machine_max_acceleration_y");
+ if (machine_max_acceleration_y != nullptr)
+ m_time_processor.machine_limits.machine_max_acceleration_y.values = machine_max_acceleration_y->values;
+
+ const ConfigOptionFloats* machine_max_acceleration_z = config.option("machine_max_acceleration_z");
+ if (machine_max_acceleration_z != nullptr)
+ m_time_processor.machine_limits.machine_max_acceleration_z.values = machine_max_acceleration_z->values;
+
+ const ConfigOptionFloats* machine_max_acceleration_e = config.option("machine_max_acceleration_e");
+ if (machine_max_acceleration_e != nullptr)
+ m_time_processor.machine_limits.machine_max_acceleration_e.values = machine_max_acceleration_e->values;
+
+ const ConfigOptionFloats* machine_max_feedrate_x = config.option("machine_max_feedrate_x");
+ if (machine_max_feedrate_x != nullptr)
+ m_time_processor.machine_limits.machine_max_feedrate_x.values = machine_max_feedrate_x->values;
+
+ const ConfigOptionFloats* machine_max_feedrate_y = config.option("machine_max_feedrate_y");
+ if (machine_max_feedrate_y != nullptr)
+ m_time_processor.machine_limits.machine_max_feedrate_y.values = machine_max_feedrate_y->values;
+
+ const ConfigOptionFloats* machine_max_feedrate_z = config.option("machine_max_feedrate_z");
+ if (machine_max_feedrate_z != nullptr)
+ m_time_processor.machine_limits.machine_max_feedrate_z.values = machine_max_feedrate_z->values;
+
+ const ConfigOptionFloats* machine_max_feedrate_e = config.option("machine_max_feedrate_e");
+ if (machine_max_feedrate_e != nullptr)
+ m_time_processor.machine_limits.machine_max_feedrate_e.values = machine_max_feedrate_e->values;
+
+ const ConfigOptionFloats* machine_max_jerk_x = config.option("machine_max_jerk_x");
+ if (machine_max_jerk_x != nullptr)
+ m_time_processor.machine_limits.machine_max_jerk_x.values = machine_max_jerk_x->values;
+
+ const ConfigOptionFloats* machine_max_jerk_y = config.option("machine_max_jerk_y");
+ if (machine_max_jerk_y != nullptr)
+ m_time_processor.machine_limits.machine_max_jerk_y.values = machine_max_jerk_y->values;
+
+ const ConfigOptionFloats* machine_max_jerk_z = config.option("machine_max_jerkz");
+ if (machine_max_jerk_z != nullptr)
+ m_time_processor.machine_limits.machine_max_jerk_z.values = machine_max_jerk_z->values;
+
+ const ConfigOptionFloats* machine_max_jerk_e = config.option("machine_max_jerk_e");
+ if (machine_max_jerk_e != nullptr)
+ m_time_processor.machine_limits.machine_max_jerk_e.values = machine_max_jerk_e->values;
+
+ const ConfigOptionFloats* machine_max_acceleration_extruding = config.option("machine_max_acceleration_extruding");
+ if (machine_max_acceleration_extruding != nullptr)
+ m_time_processor.machine_limits.machine_max_acceleration_extruding.values = machine_max_acceleration_extruding->values;
+
+ const ConfigOptionFloats* machine_max_acceleration_retracting = config.option("machine_max_acceleration_retracting");
+ if (machine_max_acceleration_retracting != nullptr)
+ m_time_processor.machine_limits.machine_max_acceleration_retracting.values = machine_max_acceleration_retracting->values;
+
+ const ConfigOptionFloats* machine_min_extruding_rate = config.option("machine_min_extruding_rate");
+ if (machine_min_extruding_rate != nullptr)
+ m_time_processor.machine_limits.machine_min_extruding_rate.values = machine_min_extruding_rate->values;
+
+ const ConfigOptionFloats* machine_min_travel_rate = config.option("machine_min_travel_rate");
+ if (machine_min_travel_rate != nullptr)
+ m_time_processor.machine_limits.machine_min_travel_rate.values = machine_min_travel_rate->values;
+
+ for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
+ float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, i);
+ m_time_processor.machines[i].max_acceleration = max_acceleration;
+ m_time_processor.machines[i].acceleration = (max_acceleration > 0.0f) ? max_acceleration : DEFAULT_ACCELERATION;
+ }
+}
+
+void GCodeProcessor::enable_stealth_time_estimator(bool enabled)
+{
+ m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].enabled = enabled;
+}
+
+void GCodeProcessor::reset()
+{
+ static const size_t Min_Extruder_Count = 5;
+
+ m_units = EUnits::Millimeters;
+ m_global_positioning_type = EPositioningType::Absolute;
+ m_e_local_positioning_type = EPositioningType::Absolute;
+ m_extruder_offsets = std::vector(Min_Extruder_Count, Vec3f::Zero());
+ m_flavor = gcfRepRap;
+
+ m_start_position = { 0.0f, 0.0f, 0.0f, 0.0f };
+ m_end_position = { 0.0f, 0.0f, 0.0f, 0.0f };
+ m_origin = { 0.0f, 0.0f, 0.0f, 0.0f };
+ m_cached_position.reset();
+
+ m_feedrate = 0.0f;
+ m_width = 0.0f;
+ m_height = 0.0f;
+ m_mm3_per_mm = 0.0f;
+ m_fan_speed = 0.0f;
+
+ m_extrusion_role = erNone;
+ m_extruder_id = 0;
+ m_extruder_colors.resize(Min_Extruder_Count);
+ for (size_t i = 0; i < Min_Extruder_Count; ++i) {
+ m_extruder_colors[i] = static_cast(i);
+ }
+
+ m_filament_diameters = std::vector(Min_Extruder_Count, 1.75f);
+ m_extruded_last_z = 0.0f;
+ m_layer_id = 0;
+ m_cp_color.reset();
+
+ m_producer = EProducer::Unknown;
+ m_producers_enabled = false;
+
+ m_time_processor.reset();
+
+ m_result.reset();
+ m_result.id = ++s_result_id;
+
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+ m_mm3_per_mm_compare.reset();
+ m_height_compare.reset();
+ m_width_compare.reset();
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
+}
+
+void GCodeProcessor::process_file(const std::string& filename)
+{
+#if ENABLE_GCODE_VIEWER_STATISTICS
+ auto start_time = std::chrono::high_resolution_clock::now();
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+
+ // pre-processing
+ // parse the gcode file to detect its producer
+ if (m_producers_enabled) {
+ m_parser.parse_file(filename, [this](GCodeReader& reader, const GCodeReader::GCodeLine& line) {
+ std::string cmd = line.cmd();
+ if (cmd.length() == 0) {
+ std::string comment = line.comment();
+ if (comment.length() > 1 && detect_producer(comment))
+ m_parser.quit_parsing_file();
+ }
+ });
+
+ // if the gcode was produced by PrusaSlicer,
+ // extract the config from it
+ if (m_producer == EProducer::PrusaSlicer) {
+ DynamicPrintConfig config;
+ config.apply(FullPrintConfig::defaults());
+ config.load_from_gcode_file(filename);
+ apply_config(config);
+ }
+ }
+
+ m_result.id = ++s_result_id;
+ m_result.moves.emplace_back(MoveVertex());
+ m_parser.parse_file(filename, [this](GCodeReader& reader, const GCodeReader::GCodeLine& line) { process_gcode_line(line); });
+
+ // process the time blocks
+ for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
+ TimeMachine& machine = m_time_processor.machines[i];
+ TimeMachine::CustomGCodeTime& gcode_time = machine.gcode_time;
+ machine.calculate_time();
+ if (gcode_time.needed && gcode_time.cache != 0.0f)
+ gcode_time.times.push_back({ CustomGCode::ColorChange, gcode_time.cache });
+ }
+
+ update_estimated_times_stats();
+
+ // post-process to add M73 lines into the gcode
+ if (m_time_processor.export_remaining_time_enabled)
+ m_time_processor.post_process(filename);
+
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+ std::cout << "\n";
+ m_mm3_per_mm_compare.output();
+ m_height_compare.output();
+ m_width_compare.output();
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
+
+#if ENABLE_GCODE_VIEWER_STATISTICS
+ m_result.time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count();
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+}
+
+float GCodeProcessor::get_time(PrintEstimatedTimeStatistics::ETimeMode mode) const
+{
+ return (mode < PrintEstimatedTimeStatistics::ETimeMode::Count) ? m_time_processor.machines[static_cast(mode)].time : 0.0f;
+}
+
+std::string GCodeProcessor::get_time_dhm(PrintEstimatedTimeStatistics::ETimeMode mode) const
+{
+ return (mode < PrintEstimatedTimeStatistics::ETimeMode::Count) ? short_time(get_time_dhms(m_time_processor.machines[static_cast(mode)].time)) : std::string("N/A");
+}
+
+std::vector>> GCodeProcessor::get_custom_gcode_times(PrintEstimatedTimeStatistics::ETimeMode mode, bool include_remaining) const
+{
+ std::vector>> ret;
+ if (mode < PrintEstimatedTimeStatistics::ETimeMode::Count) {
+ const TimeMachine& machine = m_time_processor.machines[static_cast(mode)];
+ float total_time = 0.0f;
+ for (const auto& [type, time] : machine.gcode_time.times) {
+ float remaining = include_remaining ? machine.time - total_time : 0.0f;
+ ret.push_back({ type, { time, remaining } });
+ total_time += time;
+ }
+ }
+ return ret;
+}
+
+std::vector> GCodeProcessor::get_moves_time(PrintEstimatedTimeStatistics::ETimeMode mode) const
+{
+ std::vector> ret;
+ if (mode < PrintEstimatedTimeStatistics::ETimeMode::Count) {
+ for (size_t i = 0; i < m_time_processor.machines[static_cast(mode)].moves_time.size(); ++i) {
+ float time = m_time_processor.machines[static_cast(mode)].moves_time[i];
+ if (time > 0.0f)
+ ret.push_back({ static_cast(i), time });
+ }
+ }
+ return ret;
+}
+
+std::vector> GCodeProcessor::get_roles_time(PrintEstimatedTimeStatistics::ETimeMode mode) const
+{
+ std::vector> ret;
+ if (mode < PrintEstimatedTimeStatistics::ETimeMode::Count) {
+ for (size_t i = 0; i < m_time_processor.machines[static_cast(mode)].roles_time.size(); ++i) {
+ float time = m_time_processor.machines[static_cast(mode)].roles_time[i];
+ if (time > 0.0f)
+ ret.push_back({ static_cast(i), time });
+ }
+ }
+ return ret;
+}
+
+std::vector GCodeProcessor::get_layers_time(PrintEstimatedTimeStatistics::ETimeMode mode) const
+{
+ return (mode < PrintEstimatedTimeStatistics::ETimeMode::Count) ?
+ m_time_processor.machines[static_cast(mode)].layers_time :
+ std::vector();
+}
+
+void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line)
+{
+/* std::cout << line.raw() << std::endl; */
+
+ // update start position
+ m_start_position = m_end_position;
+
+ std::string cmd = line.cmd();
+ if (cmd.length() > 1) {
+ // process command lines
+ switch (::toupper(cmd[0]))
+ {
+ case 'G':
+ {
+ switch (::atoi(&cmd[1]))
+ {
+ case 0: { process_G0(line); break; } // Move
+ case 1: { process_G1(line); break; } // Move
+ case 10: { process_G10(line); break; } // Retract
+ case 11: { process_G11(line); break; } // Unretract
+ case 20: { process_G20(line); break; } // Set Units to Inches
+ case 21: { process_G21(line); break; } // Set Units to Millimeters
+ case 22: { process_G22(line); break; } // Firmware controlled retract
+ case 23: { process_G23(line); break; } // Firmware controlled unretract
+ case 90: { process_G90(line); break; } // Set to Absolute Positioning
+ case 91: { process_G91(line); break; } // Set to Relative Positioning
+ case 92: { process_G92(line); break; } // Set Position
+ default: { break; }
+ }
+ break;
+ }
+ case 'M':
+ {
+ switch (::atoi(&cmd[1]))
+ {
+ case 1: { process_M1(line); break; } // Sleep or Conditional stop
+ case 82: { process_M82(line); break; } // Set extruder to absolute mode
+ case 83: { process_M83(line); break; } // Set extruder to relative mode
+ case 106: { process_M106(line); break; } // Set fan speed
+ case 107: { process_M107(line); break; } // Disable fan
+ case 108: { process_M108(line); break; } // Set tool (Sailfish)
+ case 132: { process_M132(line); break; } // Recall stored home offsets
+ case 135: { process_M135(line); break; } // Set tool (MakerWare)
+ case 201: { process_M201(line); break; } // Set max printing acceleration
+ case 203: { process_M203(line); break; } // Set maximum feedrate
+ case 204: { process_M204(line); break; } // Set default acceleration
+ case 205: { process_M205(line); break; } // Advanced settings
+ case 221: { process_M221(line); break; } // Set extrude factor override percentage
+ case 401: { process_M401(line); break; } // Repetier: Store x, y and z position
+ case 402: { process_M402(line); break; } // Repetier: Go to stored position
+ case 566: { process_M566(line); break; } // Set allowable instantaneous speed change
+ case 702: { process_M702(line); break; } // Unload the current filament into the MK3 MMU2 unit at the end of print.
+ default: { break; }
+ }
+ break;
+ }
+ case 'T':
+ {
+ process_T(line); // Select Tool
+ break;
+ }
+ default: { break; }
+ }
+ }
+ else {
+ std::string comment = line.comment();
+ if (comment.length() > 1)
+ // process tags embedded into comments
+ process_tags(comment);
+ }
+}
+
+void GCodeProcessor::process_tags(const std::string& comment)
+{
+ // producers tags
+ if (m_producers_enabled) {
+ if (m_producer != EProducer::Unknown) {
+ if (process_producers_tags(comment))
+ return;
+ }
+ }
+
+ // extrusion role tag
+ size_t pos = comment.find(Extrusion_Role_Tag);
+ if (pos != comment.npos) {
+ m_extrusion_role = ExtrusionEntity::string_to_role(comment.substr(pos + Extrusion_Role_Tag.length()));
+ return;
+ }
+
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+ // width tag
+ pos = comment.find(Width_Tag);
+ if (pos != comment.npos) {
+ try {
+ m_width_compare.last_tag_value = std::stof(comment.substr(pos + Width_Tag.length()));
+ }
+ catch (...) {
+ BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ").";
+ }
+ return;
+ }
+
+ // height tag
+ pos = comment.find(Height_Tag);
+ if (pos != comment.npos) {
+ try {
+ m_height_compare.last_tag_value = std::stof(comment.substr(pos + Height_Tag.length()));
+ }
+ catch (...) {
+ BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ").";
+ }
+ return;
+ }
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
+
+ // color change tag
+ pos = comment.find(Color_Change_Tag);
+ if (pos != comment.npos) {
+ pos = comment.find_last_of(",T");
+ try {
+ unsigned char extruder_id = (pos == comment.npos) ? 0 : static_cast(std::stoi(comment.substr(pos + 1)));
+
+ m_extruder_colors[extruder_id] = static_cast(m_extruder_offsets.size()) + m_cp_color.counter; // color_change position in list of color for preview
+ ++m_cp_color.counter;
+ if (m_cp_color.counter == UCHAR_MAX)
+ m_cp_color.counter = 0;
+
+ if (m_extruder_id == extruder_id) {
+ m_cp_color.current = m_extruder_colors[extruder_id];
+ store_move_vertex(EMoveType::Color_change);
+ }
+
+ process_custom_gcode_time(CustomGCode::ColorChange);
+ }
+ catch (...) {
+ BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Color_Change (" << comment << ").";
+ }
+
+ return;
+ }
+
+ // pause print tag
+ pos = comment.find(Pause_Print_Tag);
+ if (pos != comment.npos) {
+ store_move_vertex(EMoveType::Pause_Print);
+ process_custom_gcode_time(CustomGCode::PausePrint);
+ return;
+ }
+
+ // custom code tag
+ pos = comment.find(Custom_Code_Tag);
+ if (pos != comment.npos) {
+ store_move_vertex(EMoveType::Custom_GCode);
+ return;
+ }
+
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+ // mm3_per_mm print tag
+ pos = comment.find(Mm3_Per_Mm_Tag);
+ if (pos != comment.npos) {
+ try {
+ m_mm3_per_mm_compare.last_tag_value = std::stof(comment.substr(pos + Mm3_Per_Mm_Tag.length()));
+ }
+ catch (...) {
+ BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Mm3_Per_Mm (" << comment << ").";
+ }
+ return;
+ }
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
+
+ // layer change tag
+ pos = comment.find(Layer_Change_Tag);
+ if (pos != comment.npos) {
+ ++m_layer_id;
+ return;
+ }
+}
+
+bool GCodeProcessor::process_producers_tags(const std::string& comment)
+{
+ switch (m_producer)
+ {
+ case EProducer::PrusaSlicer: { return process_prusaslicer_tags(comment); }
+ case EProducer::Cura: { return process_cura_tags(comment); }
+ case EProducer::Simplify3D: { return process_simplify3d_tags(comment); }
+ case EProducer::CraftWare: { return process_craftware_tags(comment); }
+ case EProducer::ideaMaker: { return process_ideamaker_tags(comment); }
+ default: { return false; }
+ }
+}
+
+bool GCodeProcessor::process_prusaslicer_tags(const std::string& comment)
+{
+ return false;
+}
+
+bool GCodeProcessor::process_cura_tags(const std::string& comment)
+{
+ // TYPE -> extrusion role
+ std::string tag = "TYPE:";
+ size_t pos = comment.find(tag);
+ if (pos != comment.npos) {
+ std::string type = comment.substr(pos + tag.length());
+ if (type == "SKIRT")
+ m_extrusion_role = erSkirt;
+ else if (type == "WALL-OUTER")
+ m_extrusion_role = erExternalPerimeter;
+ else if (type == "WALL-INNER")
+ m_extrusion_role = erPerimeter;
+ else if (type == "SKIN")
+ m_extrusion_role = erSolidInfill;
+ else if (type == "FILL")
+ m_extrusion_role = erInternalInfill;
+ else if (type == "SUPPORT")
+ m_extrusion_role = erSupportMaterial;
+ else if (type == "SUPPORT-INTERFACE")
+ m_extrusion_role = erSupportMaterialInterface;
+ else if (type == "PRIME-TOWER")
+ m_extrusion_role = erWipeTower;
+ else {
+ m_extrusion_role = erNone;
+ BOOST_LOG_TRIVIAL(warning) << "GCodeProcessor found unknown extrusion role: " << type;
+ }
+
+ return true;
+ }
+
+ // flavor
+ tag = "FLAVOR:";
+ pos = comment.find(tag);
+ if (pos != comment.npos) {
+ std::string flavor = comment.substr(pos + tag.length());
+ if (flavor == "BFB")
+ m_flavor = gcfMarlin; // << ???????????????????????
+ else if (flavor == "Mach3")
+ m_flavor = gcfMach3;
+ else if (flavor == "Makerbot")
+ m_flavor = gcfMakerWare;
+ else if (flavor == "UltiGCode")
+ m_flavor = gcfMarlin; // << ???????????????????????
+ else if (flavor == "Marlin(Volumetric)")
+ m_flavor = gcfMarlin; // << ???????????????????????
+ else if (flavor == "Griffin")
+ m_flavor = gcfMarlin; // << ???????????????????????
+ else if (flavor == "Repetier")
+ m_flavor = gcfRepetier;
+ else if (flavor == "RepRap")
+ m_flavor = gcfRepRap;
+ else if (flavor == "Marlin")
+ m_flavor = gcfMarlin;
+ else
+ BOOST_LOG_TRIVIAL(warning) << "GCodeProcessor found unknown flavor: " << flavor;
+
+ return true;
+ }
+
+ return false;
+}
+
+bool GCodeProcessor::process_simplify3d_tags(const std::string& comment)
+{
+ // extrusion roles
+
+ // ; skirt
+ size_t pos = comment.find(" skirt");
+ if (pos == 0) {
+ m_extrusion_role = erSkirt;
+ return true;
+ }
+
+ // ; outer perimeter
+ pos = comment.find(" outer perimeter");
+ if (pos == 0) {
+ m_extrusion_role = erExternalPerimeter;
+ return true;
+ }
+
+ // ; inner perimeter
+ pos = comment.find(" inner perimeter");
+ if (pos == 0) {
+ m_extrusion_role = erPerimeter;
+ return true;
+ }
+
+ // ; gap fill
+ pos = comment.find(" gap fill");
+ if (pos == 0) {
+ m_extrusion_role = erGapFill;
+ return true;
+ }
+
+ // ; infill
+ pos = comment.find(" infill");
+ if (pos == 0) {
+ m_extrusion_role = erInternalInfill;
+ return true;
+ }
+
+ // ; solid layer
+ pos = comment.find(" solid layer");
+ if (pos == 0) {
+ m_extrusion_role = erNone; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+ return true;
+ }
+
+ // ; bridge
+ pos = comment.find(" bridge");
+ if (pos == 0) {
+ m_extrusion_role = erBridgeInfill;
+ return true;
+ }
+
+ // ; support
+ pos = comment.find(" support");
+ if (pos == 0) {
+ m_extrusion_role = erSupportMaterial;
+ return true;
+ }
+
+ // ; prime pillar
+ pos = comment.find(" prime pillar");
+ if (pos == 0) {
+ m_extrusion_role = erWipeTower;
+ return true;
+ }
+
+ // ; ooze shield
+ pos = comment.find(" ooze shield");
+ if (pos == 0) {
+ m_extrusion_role = erNone; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+ return true;
+ }
+
+ // ; raft
+ pos = comment.find(" raft");
+ if (pos == 0) {
+ m_extrusion_role = erSkirt;
+ return true;
+ }
+
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+ // geometry
+
+ // ; tool
+ std::string tag = " tool";
+ pos = comment.find(tag);
+ if (pos == 0) {
+ std::string data = comment.substr(pos + tag.length());
+ std::string h_tag = "H";
+ size_t h_start = data.find(h_tag);
+ size_t h_end = data.find_first_of(' ', h_start);
+ std::string w_tag = "W";
+ size_t w_start = data.find(w_tag);
+ size_t w_end = data.find_first_of(' ', w_start);
+ if (h_start != data.npos) {
+ try {
+ m_height_compare.last_tag_value = std::stof(data.substr(h_start + 1, (h_end != data.npos) ? h_end - h_start - 1 : h_end));
+ }
+ catch (...) {
+ BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ").";
+ }
+ }
+ if (w_start != data.npos) {
+ try {
+ m_width_compare.last_tag_value = std::stof(data.substr(w_start + 1, (w_end != data.npos) ? w_end - w_start - 1 : w_end));
+ }
+ catch (...) {
+ BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ").";
+ }
+ }
+
+ return true;
+ }
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
+
+ return false;
+}
+
+bool GCodeProcessor::process_craftware_tags(const std::string& comment)
+{
+ // segType -> extrusion role
+ std::string tag = "segType:";
+ size_t pos = comment.find(tag);
+ if (pos != comment.npos) {
+ std::string type = comment.substr(pos + tag.length());
+ if (type == "Skirt")
+ m_extrusion_role = erSkirt;
+ else if (type == "Perimeter")
+ m_extrusion_role = erExternalPerimeter;
+ else if (type == "HShell")
+ m_extrusion_role = erNone; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+ else if (type == "InnerHair")
+ m_extrusion_role = erNone; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+ else if (type == "Loop")
+ m_extrusion_role = erNone; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+ else if (type == "Infill")
+ m_extrusion_role = erInternalInfill;
+ else if (type == "Raft")
+ m_extrusion_role = erSkirt;
+ else if (type == "Support")
+ m_extrusion_role = erSupportMaterial;
+ else if (type == "SupportTouch")
+ m_extrusion_role = erSupportMaterial;
+ else if (type == "SoftSupport")
+ m_extrusion_role = erSupportMaterialInterface;
+ else if (type == "Pillar")
+ m_extrusion_role = erWipeTower;
+ else {
+ m_extrusion_role = erNone;
+ BOOST_LOG_TRIVIAL(warning) << "GCodeProcessor found unknown extrusion role: " << type;
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+bool GCodeProcessor::process_ideamaker_tags(const std::string& comment)
+{
+ // TYPE -> extrusion role
+ std::string tag = "TYPE:";
+ size_t pos = comment.find(tag);
+ if (pos != comment.npos) {
+ std::string type = comment.substr(pos + tag.length());
+ if (type == "RAFT")
+ m_extrusion_role = erSkirt;
+ else if (type == "WALL-OUTER")
+ m_extrusion_role = erExternalPerimeter;
+ else if (type == "WALL-INNER")
+ m_extrusion_role = erPerimeter;
+ else if (type == "SOLID-FILL")
+ m_extrusion_role = erSolidInfill;
+ else if (type == "FILL")
+ m_extrusion_role = erInternalInfill;
+ else if (type == "BRIDGE")
+ m_extrusion_role = erBridgeInfill;
+ else if (type == "SUPPORT")
+ m_extrusion_role = erSupportMaterial;
+ else {
+ m_extrusion_role = erNone;
+ BOOST_LOG_TRIVIAL(warning) << "GCodeProcessor found unknown extrusion role: " << type;
+ }
+ return true;
+ }
+
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+ // geometry
+
+ // width
+ tag = "WIDTH:";
+ pos = comment.find(tag);
+ if (pos != comment.npos) {
+ try {
+ m_width_compare.last_tag_value = std::stof(comment.substr(pos + tag.length()));
+ }
+ catch (...) {
+ BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ").";
+ }
+ return true;
+ }
+
+ // height
+ tag = "HEIGHT:";
+ pos = comment.find(tag);
+ if (pos != comment.npos) {
+ try {
+ m_height_compare.last_tag_value = std::stof(comment.substr(pos + tag.length()));
+ }
+ catch (...) {
+ BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ").";
+ }
+ return true;
+ }
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
+
+ return false;
+}
+
+bool GCodeProcessor::detect_producer(const std::string& comment)
+{
+ for (const auto& [id, search_string] : Producers) {
+ size_t pos = comment.find(search_string);
+ if (pos != comment.npos) {
+ m_producer = id;
+ BOOST_LOG_TRIVIAL(info) << "Detected gcode producer: " << search_string;
+ return true;
+ }
+ }
+ return false;
+}
+
+void GCodeProcessor::process_G0(const GCodeReader::GCodeLine& line)
+{
+ process_G1(line);
+}
+
+void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
+{
+ auto absolute_position = [this](Axis axis, const GCodeReader::GCodeLine& lineG1)
+ {
+ bool is_relative = (m_global_positioning_type == EPositioningType::Relative);
+ if (axis == E)
+ is_relative |= (m_e_local_positioning_type == EPositioningType::Relative);
+
+ if (lineG1.has(Slic3r::Axis(axis))) {
+ float lengthsScaleFactor = (m_units == EUnits::Inches) ? INCHES_TO_MM : 1.0f;
+ float ret = lineG1.value(Slic3r::Axis(axis)) * lengthsScaleFactor;
+ return is_relative ? m_start_position[axis] + ret : m_origin[axis] + ret;
+ }
+ else
+ return m_start_position[axis];
+ };
+
+ auto move_type = [this](const AxisCoords& delta_pos) {
+ EMoveType type = EMoveType::Noop;
+
+ if (delta_pos[E] < 0.0f) {
+ type = (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f || delta_pos[Z] != 0.0f) ? EMoveType::Travel : EMoveType::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 = EMoveType::Unretract;
+ else if (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f)
+ type = EMoveType::Extrude;
+ }
+ else if (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f || delta_pos[Z] != 0.0f)
+ type = EMoveType::Travel;
+
+ return type;
+ };
+
+ // enable processing of lines M201/M203/M204/M205
+ m_time_processor.machine_envelope_processing_enabled = true;
+
+ // updates axes positions from line
+ for (unsigned char a = X; a <= E; ++a) {
+ m_end_position[a] = absolute_position((Axis)a, line);
+ }
+
+ // updates feedrate from line, if present
+ if (line.has_f())
+ m_feedrate = line.f() * MMMIN_TO_MMSEC;
+
+ // calculates movement deltas
+ float max_abs_delta = 0.0f;
+ AxisCoords delta_pos;
+ for (unsigned char a = X; a <= E; ++a) {
+ delta_pos[a] = m_end_position[a] - m_start_position[a];
+ max_abs_delta = std::max(max_abs_delta, std::abs(delta_pos[a]));
+ }
+
+ // no displacement, return
+ if (max_abs_delta == 0.0f)
+ return;
+
+ EMoveType type = move_type(delta_pos);
+ if (type == EMoveType::Extrude && m_end_position[Z] == 0.0f)
+ type = EMoveType::Travel;
+
+ if (type == EMoveType::Extrude) {
+ float d_xyz = std::sqrt(sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z]));
+ float filament_diameter = (static_cast(m_extruder_id) < m_filament_diameters.size()) ? m_filament_diameters[m_extruder_id] : m_filament_diameters.back();
+ float filament_radius = 0.5f * filament_diameter;
+ float area_filament_cross_section = static_cast(M_PI) * sqr(filament_radius);
+ float volume_extruded_filament = area_filament_cross_section * delta_pos[E];
+ float area_toolpath_cross_section = volume_extruded_filament / d_xyz;
+
+ // volume extruded filament / tool displacement = area toolpath cross section
+ m_mm3_per_mm = area_toolpath_cross_section;
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+ m_mm3_per_mm_compare.update(area_toolpath_cross_section, m_extrusion_role);
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
+
+ if (m_end_position[Z] > m_extruded_last_z + EPSILON) {
+ m_height = m_end_position[Z] - m_extruded_last_z;
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+ m_height_compare.update(m_height, m_extrusion_role);
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
+ m_extruded_last_z = m_end_position[Z];
+ }
+
+ if (m_extrusion_role == erExternalPerimeter)
+ // cross section: rectangle
+ m_width = delta_pos[E] * static_cast(M_PI * sqr(1.05 * filament_radius)) / (d_xyz * m_height);
+ else if (m_extrusion_role == erBridgeInfill || m_extrusion_role == erNone)
+ // cross section: circle
+ m_width = static_cast(m_filament_diameters[m_extruder_id]) * std::sqrt(delta_pos[E] / d_xyz);
+ else
+ // cross section: rectangle + 2 semicircles
+ m_width = delta_pos[E] * static_cast(M_PI * sqr(filament_radius)) / (d_xyz * m_height) + static_cast(1.0 - 0.25 * M_PI) * m_height;
+
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+ m_width_compare.update(m_width, m_extrusion_role);
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
+ }
+
+ if (type == EMoveType::Extrude && (m_extrusion_role == erCustom || m_width == 0.0f || m_height == 0.0f))
+ type = EMoveType::Travel;
+
+ // time estimate section
+ auto move_length = [](const AxisCoords& delta_pos) {
+ float sq_xyz_length = sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z]);
+ return (sq_xyz_length > 0.0f) ? std::sqrt(sq_xyz_length) : std::abs(delta_pos[E]);
+ };
+
+ auto is_extrusion_only_move = [](const AxisCoords& delta_pos) {
+ return delta_pos[X] == 0.0f && delta_pos[Y] == 0.0f && delta_pos[Z] == 0.0f && delta_pos[E] != 0.0f;
+ };
+
+ float distance = move_length(delta_pos);
+ assert(distance != 0.0f);
+ float inv_distance = 1.0f / distance;
+
+ for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
+ TimeMachine& machine = m_time_processor.machines[i];
+ if (!machine.enabled)
+ continue;
+
+ TimeMachine::State& curr = machine.curr;
+ TimeMachine::State& prev = machine.prev;
+ std::vector& blocks = machine.blocks;
+
+ curr.feedrate = (delta_pos[E] == 0.0f) ?
+ minimum_travel_feedrate(static_cast(i), m_feedrate) :
+ minimum_feedrate(static_cast(i), m_feedrate);
+
+ TimeBlock block;
+ block.move_type = type;
+ block.role = m_extrusion_role;
+ block.distance = distance;
+ block.layer_id = m_layer_id;
+
+ // calculates block cruise feedrate
+ float min_feedrate_factor = 1.0f;
+ for (unsigned char a = X; a <= E; ++a) {
+ curr.axis_feedrate[a] = curr.feedrate * delta_pos[a] * inv_distance;
+ if (a == E)
+ curr.axis_feedrate[a] *= machine.extrude_factor_override_percentage;
+
+ curr.abs_axis_feedrate[a] = std::abs(curr.axis_feedrate[a]);
+ if (curr.abs_axis_feedrate[a] != 0.0f) {
+ float axis_max_feedrate = get_axis_max_feedrate(static_cast(i), static_cast(a));
+ if (axis_max_feedrate != 0.0f)
+ min_feedrate_factor = std::min(min_feedrate_factor, axis_max_feedrate / curr.abs_axis_feedrate[a]);
+ }
+ }
+
+ block.feedrate_profile.cruise = min_feedrate_factor * curr.feedrate;
+
+ if (min_feedrate_factor < 1.0f) {
+ for (unsigned char a = X; a <= E; ++a) {
+ curr.axis_feedrate[a] *= min_feedrate_factor;
+ curr.abs_axis_feedrate[a] *= min_feedrate_factor;
+ }
+ }
+
+ // calculates block acceleration
+ float acceleration = is_extrusion_only_move(delta_pos) ?
+ get_retract_acceleration(static_cast(i)) :
+ get_acceleration(static_cast(i));
+
+ for (unsigned char a = X; a <= E; ++a) {
+ float axis_max_acceleration = get_axis_max_acceleration(static_cast(i), static_cast(a));
+ if (acceleration * std::abs(delta_pos[a]) * inv_distance > axis_max_acceleration)
+ acceleration = axis_max_acceleration;
+ }
+
+ block.acceleration = acceleration;
+
+ // calculates block exit feedrate
+ curr.safe_feedrate = block.feedrate_profile.cruise;
+
+ for (unsigned char a = X; a <= E; ++a) {
+ float axis_max_jerk = get_axis_max_jerk(static_cast(i), static_cast(a));
+ if (curr.abs_axis_feedrate[a] > axis_max_jerk)
+ curr.safe_feedrate = std::min(curr.safe_feedrate, axis_max_jerk);
+ }
+
+ block.feedrate_profile.exit = curr.safe_feedrate;
+
+ static const float PREVIOUS_FEEDRATE_THRESHOLD = 0.0001f;
+
+ // calculates block entry feedrate
+ float vmax_junction = curr.safe_feedrate;
+ if (!blocks.empty() && prev.feedrate > PREVIOUS_FEEDRATE_THRESHOLD) {
+ bool prev_speed_larger = prev.feedrate > block.feedrate_profile.cruise;
+ float smaller_speed_factor = prev_speed_larger ? (block.feedrate_profile.cruise / prev.feedrate) : (prev.feedrate / block.feedrate_profile.cruise);
+ // Pick the smaller of the nominal speeds. Higher speed shall not be achieved at the junction during coasting.
+ vmax_junction = prev_speed_larger ? block.feedrate_profile.cruise : prev.feedrate;
+
+ float v_factor = 1.0f;
+ bool limited = false;
+
+ for (unsigned char a = X; a <= E; ++a) {
+ // Limit an axis. We have to differentiate coasting from the reversal of an axis movement, or a full stop.
+ float v_exit = prev.axis_feedrate[a];
+ float v_entry = curr.axis_feedrate[a];
+
+ if (prev_speed_larger)
+ v_exit *= smaller_speed_factor;
+
+ if (limited) {
+ v_exit *= v_factor;
+ v_entry *= v_factor;
+ }
+
+ // Calculate the jerk depending on whether the axis is coasting in the same direction or reversing a direction.
+ float jerk =
+ (v_exit > v_entry) ?
+ (((v_entry > 0.0f) || (v_exit < 0.0f)) ?
+ // coasting
+ (v_exit - v_entry) :
+ // axis reversal
+ std::max(v_exit, -v_entry)) :
+ // v_exit <= v_entry
+ (((v_entry < 0.0f) || (v_exit > 0.0f)) ?
+ // coasting
+ (v_entry - v_exit) :
+ // axis reversal
+ std::max(-v_exit, v_entry));
+
+ float axis_max_jerk = get_axis_max_jerk(static_cast(i), static_cast(a));
+ if (jerk > axis_max_jerk) {
+ v_factor *= axis_max_jerk / jerk;
+ limited = true;
+ }
+ }
+
+ if (limited)
+ vmax_junction *= v_factor;
+
+ // Now the transition velocity is known, which maximizes the shared exit / entry velocity while
+ // respecting the jerk factors, it may be possible, that applying separate safe exit / entry velocities will achieve faster prints.
+ float vmax_junction_threshold = vmax_junction * 0.99f;
+
+ // Not coasting. The machine will stop and start the movements anyway, better to start the segment from start.
+ if ((prev.safe_feedrate > vmax_junction_threshold) && (curr.safe_feedrate > vmax_junction_threshold))
+ vmax_junction = curr.safe_feedrate;
+ }
+
+ float v_allowable = max_allowable_speed(-acceleration, curr.safe_feedrate, block.distance);
+ block.feedrate_profile.entry = std::min(vmax_junction, v_allowable);
+
+ block.max_entry_speed = vmax_junction;
+ block.flags.nominal_length = (block.feedrate_profile.cruise <= v_allowable);
+ block.flags.recalculate = true;
+ block.safe_feedrate = curr.safe_feedrate;
+
+ // calculates block trapezoid
+ block.calculate_trapezoid();
+
+ // updates previous
+ prev = curr;
+
+ blocks.push_back(block);
+
+ if (blocks.size() > TimeProcessor::Planner::refresh_threshold)
+ machine.calculate_time(TimeProcessor::Planner::queue_size);
+ }
+
+ // store move
+ store_move_vertex(type);
+}
+
+void GCodeProcessor::process_G10(const GCodeReader::GCodeLine& line)
+{
+ // stores retract move
+ store_move_vertex(EMoveType::Retract);
+}
+
+void GCodeProcessor::process_G11(const GCodeReader::GCodeLine& line)
+{
+ // stores unretract move
+ store_move_vertex(EMoveType::Unretract);
+}
+
+void GCodeProcessor::process_G20(const GCodeReader::GCodeLine& line)
+{
+ m_units = EUnits::Inches;
+}
+
+void GCodeProcessor::process_G21(const GCodeReader::GCodeLine& line)
+{
+ m_units = EUnits::Millimeters;
+}
+
+void GCodeProcessor::process_G22(const GCodeReader::GCodeLine& line)
+{
+ // stores retract move
+ store_move_vertex(EMoveType::Retract);
+}
+
+void GCodeProcessor::process_G23(const GCodeReader::GCodeLine& line)
+{
+ // stores unretract move
+ store_move_vertex(EMoveType::Unretract);
+}
+
+void GCodeProcessor::process_G90(const GCodeReader::GCodeLine& line)
+{
+ m_global_positioning_type = EPositioningType::Absolute;
+}
+
+void GCodeProcessor::process_G91(const GCodeReader::GCodeLine& line)
+{
+ m_global_positioning_type = EPositioningType::Relative;
+}
+
+void GCodeProcessor::process_G92(const GCodeReader::GCodeLine& line)
+{
+ float lengths_scale_factor = (m_units == EUnits::Inches) ? INCHES_TO_MM : 1.0f;
+ bool any_found = false;
+
+ if (line.has_x()) {
+ m_origin[X] = m_end_position[X] - line.x() * lengths_scale_factor;
+ any_found = true;
+ }
+
+ if (line.has_y()) {
+ m_origin[Y] = m_end_position[Y] - line.y() * lengths_scale_factor;
+ any_found = true;
+ }
+
+ if (line.has_z()) {
+ m_origin[Z] = m_end_position[Z] - line.z() * lengths_scale_factor;
+ any_found = true;
+ }
+
+ if (line.has_e()) {
+ // extruder coordinate can grow to the point where its float representation does not allow for proper addition with small increments,
+ // we set the value taken from the G92 line as the new current position for it
+ m_end_position[E] = line.e() * lengths_scale_factor;
+ any_found = true;
+ }
+ else
+ simulate_st_synchronize();
+
+ if (!any_found && !line.has_unknown_axis()) {
+ // The G92 may be called for axes that PrusaSlicer does not recognize, for example see GH issue #3510,
+ // where G92 A0 B0 is called although the extruder axis is till E.
+ for (unsigned char a = X; a <= E; ++a) {
+ m_origin[a] = m_end_position[a];
+ }
+ }
+}
+
+void GCodeProcessor::process_M1(const GCodeReader::GCodeLine& line)
+{
+ simulate_st_synchronize();
+}
+
+void GCodeProcessor::process_M82(const GCodeReader::GCodeLine& line)
+{
+ m_e_local_positioning_type = EPositioningType::Absolute;
+}
+
+void GCodeProcessor::process_M83(const GCodeReader::GCodeLine& line)
+{
+ m_e_local_positioning_type = EPositioningType::Relative;
+}
+
+void GCodeProcessor::process_M106(const GCodeReader::GCodeLine& line)
+{
+ if (!line.has('P')) {
+ // The absence of P means the print cooling fan, so ignore anything else.
+ float new_fan_speed;
+ if (line.has_value('S', new_fan_speed))
+ m_fan_speed = (100.0f / 255.0f) * new_fan_speed;
+ else
+ m_fan_speed = 100.0f;
+ }
+}
+
+void GCodeProcessor::process_M107(const GCodeReader::GCodeLine& line)
+{
+ m_fan_speed = 0.0f;
+}
+
+void GCodeProcessor::process_M108(const GCodeReader::GCodeLine& line)
+{
+ // These M-codes are used by Sailfish to change active tool.
+ // They have to be processed otherwise toolchanges will be unrecognised
+ // by the analyzer - see https://github.com/prusa3d/PrusaSlicer/issues/2566
+
+ if (m_flavor != gcfSailfish)
+ return;
+
+ std::string cmd = line.raw();
+ size_t pos = cmd.find("T");
+ if (pos != std::string::npos)
+ process_T(cmd.substr(pos));
+}
+
+void GCodeProcessor::process_M132(const GCodeReader::GCodeLine& line)
+{
+ // This command is used by Makerbot to load the current home position from EEPROM
+ // see: https://github.com/makerbot/s3g/blob/master/doc/GCodeProtocol.md
+ // Using this command to reset the axis origin to zero helps in fixing: https://github.com/prusa3d/PrusaSlicer/issues/3082
+
+ if (line.has_x())
+ m_origin[X] = 0.0f;
+
+ if (line.has_y())
+ m_origin[Y] = 0.0f;
+
+ if (line.has_z())
+ m_origin[Z] = 0.0f;
+
+ if (line.has_e())
+ m_origin[E] = 0.0f;
+}
+
+void GCodeProcessor::process_M135(const GCodeReader::GCodeLine& line)
+{
+ // These M-codes are used by MakerWare to change active tool.
+ // They have to be processed otherwise toolchanges will be unrecognised
+ // by the analyzer - see https://github.com/prusa3d/PrusaSlicer/issues/2566
+
+ if (m_flavor != gcfMakerWare)
+ return;
+
+ std::string cmd = line.raw();
+ size_t pos = cmd.find("T");
+ if (pos != std::string::npos)
+ process_T(cmd.substr(pos));
+}
+
+void GCodeProcessor::process_M201(const GCodeReader::GCodeLine& line)
+{
+ if (!m_time_processor.machine_envelope_processing_enabled)
+ return;
+
+ // see http://reprap.org/wiki/G-code#M201:_Set_max_printing_acceleration
+ float factor = (m_flavor != gcfRepRap && m_units == EUnits::Inches) ? INCHES_TO_MM : 1.0f;
+
+ for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
+ if (line.has_x())
+ set_option_value(m_time_processor.machine_limits.machine_max_acceleration_x, i, line.x() * factor);
+
+ if (line.has_y())
+ set_option_value(m_time_processor.machine_limits.machine_max_acceleration_y, i, line.y() * factor);
+
+ if (line.has_z())
+ set_option_value(m_time_processor.machine_limits.machine_max_acceleration_z, i, line.z() * factor);
+
+ if (line.has_e())
+ set_option_value(m_time_processor.machine_limits.machine_max_acceleration_e, i, line.e() * factor);
+ }
+}
+
+void GCodeProcessor::process_M203(const GCodeReader::GCodeLine& line)
+{
+ if (!m_time_processor.machine_envelope_processing_enabled)
+ return;
+
+ // see http://reprap.org/wiki/G-code#M203:_Set_maximum_feedrate
+ if (m_flavor == gcfRepetier)
+ return;
+
+ // see http://reprap.org/wiki/G-code#M203:_Set_maximum_feedrate
+ // http://smoothieware.org/supported-g-codes
+ float factor = (m_flavor == gcfMarlin || m_flavor == gcfSmoothie) ? 1.0f : MMMIN_TO_MMSEC;
+
+ for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
+ if (line.has_x())
+ set_option_value(m_time_processor.machine_limits.machine_max_feedrate_x, i, line.x() * factor);
+
+ if (line.has_y())
+ set_option_value(m_time_processor.machine_limits.machine_max_feedrate_y, i, line.y() * factor);
+
+ if (line.has_z())
+ set_option_value(m_time_processor.machine_limits.machine_max_feedrate_z, i, line.z() * factor);
+
+ if (line.has_e())
+ set_option_value(m_time_processor.machine_limits.machine_max_feedrate_e, i, line.e() * factor);
+ }
+}
+
+void GCodeProcessor::process_M204(const GCodeReader::GCodeLine& line)
+{
+ if (!m_time_processor.machine_envelope_processing_enabled)
+ return;
+
+ float value;
+ for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
+ if (line.has_value('S', value)) {
+ // Legacy acceleration format. This format is used by the legacy Marlin, MK2 or MK3 firmware,
+ // and it is also generated by Slic3r to control acceleration per extrusion type
+ // (there is a separate acceleration settings in Slicer for perimeter, first layer etc).
+ set_acceleration(static_cast(i), value);
+ if (line.has_value('T', value))
+ set_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, i, value);
+ }
+ else {
+ // New acceleration format, compatible with the upstream Marlin.
+ if (line.has_value('P', value))
+ set_acceleration(static_cast(i), value);
+ if (line.has_value('R', value))
+ set_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, i, value);
+ if (line.has_value('T', value)) {
+ // Interpret the T value as the travel acceleration in the new Marlin format.
+ //FIXME Prusa3D firmware currently does not support travel acceleration value independent from the extruding acceleration value.
+ // set_travel_acceleration(value);
+ }
+ }
+ }
+}
+
+void GCodeProcessor::process_M205(const GCodeReader::GCodeLine& line)
+{
+ if (!m_time_processor.machine_envelope_processing_enabled)
+ return;
+
+ for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
+ if (line.has_x()) {
+ float max_jerk = line.x();
+ set_option_value(m_time_processor.machine_limits.machine_max_jerk_x, i, max_jerk);
+ set_option_value(m_time_processor.machine_limits.machine_max_jerk_y, i, max_jerk);
+ }
+
+ if (line.has_y())
+ set_option_value(m_time_processor.machine_limits.machine_max_jerk_y, i, line.y());
+
+ if (line.has_z())
+ set_option_value(m_time_processor.machine_limits.machine_max_jerk_z, i, line.z());
+
+ if (line.has_e())
+ set_option_value(m_time_processor.machine_limits.machine_max_jerk_e, i, line.e());
+
+ float value;
+ if (line.has_value('S', value))
+ set_option_value(m_time_processor.machine_limits.machine_min_extruding_rate, i, value);
+
+ if (line.has_value('T', value))
+ set_option_value(m_time_processor.machine_limits.machine_min_travel_rate, i, value);
+ }
+}
+
+void GCodeProcessor::process_M221(const GCodeReader::GCodeLine& line)
+{
+ float value_s;
+ float value_t;
+ if (line.has_value('S', value_s) && !line.has_value('T', value_t)) {
+ value_s *= 0.01f;
+ for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
+ m_time_processor.machines[i].extrude_factor_override_percentage = value_s;
+ }
+ }
+}
+
+void GCodeProcessor::process_M401(const GCodeReader::GCodeLine& line)
+{
+ if (m_flavor != gcfRepetier)
+ return;
+
+ for (unsigned char a = 0; a <= 3; ++a) {
+ m_cached_position.position[a] = m_start_position[a];
+ }
+ m_cached_position.feedrate = m_feedrate;
+}
+
+void GCodeProcessor::process_M402(const GCodeReader::GCodeLine& line)
+{
+ if (m_flavor != gcfRepetier)
+ return;
+
+ // see for reference:
+ // https://github.com/repetier/Repetier-Firmware/blob/master/src/ArduinoAVR/Repetier/Printer.cpp
+ // void Printer::GoToMemoryPosition(bool x, bool y, bool z, bool e, float feed)
+
+ bool has_xyz = !(line.has_x() || line.has_y() || line.has_z());
+
+ float p = FLT_MAX;
+ for (unsigned char a = X; a <= Z; ++a) {
+ if (has_xyz || line.has(a)) {
+ p = m_cached_position.position[a];
+ if (p != FLT_MAX)
+ m_start_position[a] = p;
+ }
+ }
+
+ p = m_cached_position.position[E];
+ if (p != FLT_MAX)
+ m_start_position[E] = p;
+
+ p = FLT_MAX;
+ if (!line.has_value(4, p))
+ p = m_cached_position.feedrate;
+
+ if (p != FLT_MAX)
+ m_feedrate = p;
+}
+
+void GCodeProcessor::process_M566(const GCodeReader::GCodeLine& line)
+{
+ for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
+ if (line.has_x())
+ set_option_value(m_time_processor.machine_limits.machine_max_jerk_x, i, line.x() * MMMIN_TO_MMSEC);
+
+ if (line.has_y())
+ set_option_value(m_time_processor.machine_limits.machine_max_jerk_y, i, line.y() * MMMIN_TO_MMSEC);
+
+ if (line.has_z())
+ set_option_value(m_time_processor.machine_limits.machine_max_jerk_z, i, line.z() * MMMIN_TO_MMSEC);
+
+ if (line.has_e())
+ set_option_value(m_time_processor.machine_limits.machine_max_jerk_e, i, line.e() * MMMIN_TO_MMSEC);
+ }
+}
+
+void GCodeProcessor::process_M702(const GCodeReader::GCodeLine& line)
+{
+ if (line.has('C')) {
+ // MK3 MMU2 specific M code:
+ // M702 C is expected to be sent by the custom end G-code when finalizing a print.
+ // The MK3 unit shall unload and park the active filament into the MMU2 unit.
+ m_time_processor.extruder_unloaded = true;
+ simulate_st_synchronize(get_filament_unload_time(m_extruder_id));
+ }
+}
+
+void GCodeProcessor::process_T(const GCodeReader::GCodeLine& line)
+{
+ process_T(line.cmd());
+}
+
+void GCodeProcessor::process_T(const std::string& command)
+{
+ if (command.length() > 1) {
+ try {
+ unsigned char id = static_cast(std::stoi(command.substr(1)));
+ if (m_extruder_id != id) {
+ unsigned char extruders_count = static_cast(m_extruder_offsets.size());
+ if (id >= extruders_count)
+ BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid toolchange, maybe from a custom gcode.";
+ else {
+ unsigned char old_extruder_id = m_extruder_id;
+ m_extruder_id = id;
+ m_cp_color.current = m_extruder_colors[id];
+ // Specific to the MK3 MMU2:
+ // The initial value of extruder_unloaded is set to true indicating
+ // that the filament is parked in the MMU2 unit and there is nothing to be unloaded yet.
+ float extra_time = get_filament_unload_time(static_cast(old_extruder_id));
+ m_time_processor.extruder_unloaded = false;
+ extra_time += get_filament_load_time(static_cast(m_extruder_id));
+ simulate_st_synchronize(extra_time);
+ }
+
+ // store tool change move
+ store_move_vertex(EMoveType::Tool_change);
+ }
+ }
+ catch (...) {
+ BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid toolchange (" << command << ").";
+ }
+ }
+}
+
+void GCodeProcessor::store_move_vertex(EMoveType type)
+{
+ MoveVertex vertex = {
+ type,
+ m_extrusion_role,
+ m_extruder_id,
+ m_cp_color.current,
+ Vec3f(m_end_position[X], m_end_position[Y], m_end_position[Z]) + m_extruder_offsets[m_extruder_id],
+ m_end_position[E] - m_start_position[E],
+ m_feedrate,
+ m_width,
+ m_height,
+ m_mm3_per_mm,
+ m_fan_speed,
+ static_cast(m_result.moves.size())
+ };
+ m_result.moves.emplace_back(vertex);
+}
+
+float GCodeProcessor::minimum_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, float feedrate) const
+{
+ if (m_time_processor.machine_limits.machine_min_extruding_rate.empty())
+ return feedrate;
+
+ return std::max(feedrate, get_option_value(m_time_processor.machine_limits.machine_min_extruding_rate, static_cast(mode)));
+}
+
+float GCodeProcessor::minimum_travel_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, float feedrate) const
+{
+ if (m_time_processor.machine_limits.machine_min_travel_rate.empty())
+ return feedrate;
+
+ return std::max(feedrate, get_option_value(m_time_processor.machine_limits.machine_min_travel_rate, static_cast(mode)));
+}
+
+float GCodeProcessor::get_axis_max_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const
+{
+ switch (axis)
+ {
+ case X: { return get_option_value(m_time_processor.machine_limits.machine_max_feedrate_x, static_cast(mode)); }
+ case Y: { return get_option_value(m_time_processor.machine_limits.machine_max_feedrate_y, static_cast(mode)); }
+ case Z: { return get_option_value(m_time_processor.machine_limits.machine_max_feedrate_z, static_cast(mode)); }
+ case E: { return get_option_value(m_time_processor.machine_limits.machine_max_feedrate_e, static_cast(mode)); }
+ default: { return 0.0f; }
+ }
+}
+
+float GCodeProcessor::get_axis_max_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const
+{
+ switch (axis)
+ {
+ case X: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_x, static_cast(mode)); }
+ case Y: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_y, static_cast(mode)); }
+ case Z: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_z, static_cast(mode)); }
+ case E: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_e, static_cast(mode)); }
+ default: { return 0.0f; }
+ }
+}
+
+float GCodeProcessor::get_axis_max_jerk(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const
+{
+ switch (axis)
+ {
+ case X: { return get_option_value(m_time_processor.machine_limits.machine_max_jerk_x, static_cast(mode)); }
+ case Y: { return get_option_value(m_time_processor.machine_limits.machine_max_jerk_y, static_cast(mode)); }
+ case Z: { return get_option_value(m_time_processor.machine_limits.machine_max_jerk_z, static_cast(mode)); }
+ case E: { return get_option_value(m_time_processor.machine_limits.machine_max_jerk_e, static_cast(mode)); }
+ default: { return 0.0f; }
+ }
+}
+
+float GCodeProcessor::get_retract_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const
+{
+ return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, static_cast(mode));
+}
+
+float GCodeProcessor::get_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const
+{
+ size_t id = static_cast(mode);
+ return (id < m_time_processor.machines.size()) ? m_time_processor.machines[id].acceleration : DEFAULT_ACCELERATION;
+}
+
+void GCodeProcessor::set_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, float value)
+{
+ size_t id = static_cast(mode);
+ if (id < m_time_processor.machines.size()) {
+ m_time_processor.machines[id].acceleration = (m_time_processor.machines[id].max_acceleration == 0.0f) ? value :
+ // Clamp the acceleration with the maximum.
+ std::min(value, m_time_processor.machines[id].max_acceleration);
+ }
+}
+
+float GCodeProcessor::get_filament_load_time(size_t extruder_id)
+{
+ return (m_time_processor.filament_load_times.empty() || m_time_processor.extruder_unloaded) ?
+ 0.0f :
+ ((extruder_id < m_time_processor.filament_load_times.size()) ?
+ m_time_processor.filament_load_times[extruder_id] : m_time_processor.filament_load_times.front());
+}
+
+float GCodeProcessor::get_filament_unload_time(size_t extruder_id)
+{
+ return (m_time_processor.filament_unload_times.empty() || m_time_processor.extruder_unloaded) ?
+ 0.0f :
+ ((extruder_id < m_time_processor.filament_unload_times.size()) ?
+ m_time_processor.filament_unload_times[extruder_id] : m_time_processor.filament_unload_times.front());
+}
+
+void GCodeProcessor::process_custom_gcode_time(CustomGCode::Type code)
+{
+ for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
+ TimeMachine& machine = m_time_processor.machines[i];
+ if (!machine.enabled)
+ continue;
+
+ TimeMachine::CustomGCodeTime& gcode_time = machine.gcode_time;
+ gcode_time.needed = true;
+ //FIXME this simulates st_synchronize! is it correct?
+ // The estimated time may be longer than the real print time.
+ machine.simulate_st_synchronize();
+ if (gcode_time.cache != 0.0f) {
+ gcode_time.times.push_back({ code, gcode_time.cache });
+ gcode_time.cache = 0.0f;
+ }
+ }
+}
+
+void GCodeProcessor::simulate_st_synchronize(float additional_time)
+{
+ for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
+ m_time_processor.machines[i].simulate_st_synchronize(additional_time);
+ }
+}
+
+void GCodeProcessor::update_estimated_times_stats()
+{
+ auto update_mode = [this](PrintEstimatedTimeStatistics::ETimeMode mode) {
+ PrintEstimatedTimeStatistics::Mode& data = m_result.time_statistics.modes[static_cast(mode)];
+ data.time = get_time(mode);
+ data.custom_gcode_times = get_custom_gcode_times(mode, true);
+ data.moves_times = get_moves_time(mode);
+ data.roles_times = get_roles_time(mode);
+ data.layers_times = get_layers_time(mode);
+ };
+
+ update_mode(PrintEstimatedTimeStatistics::ETimeMode::Normal);
+ if (m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].enabled)
+ update_mode(PrintEstimatedTimeStatistics::ETimeMode::Stealth);
+ else
+ m_result.time_statistics.modes[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].reset();
+}
+
+} /* namespace Slic3r */
+
+#endif // ENABLE_GCODE_VIEWER
diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp
new file mode 100644
index 0000000000..22aeed7620
--- /dev/null
+++ b/src/libslic3r/GCode/GCodeProcessor.hpp
@@ -0,0 +1,559 @@
+#ifndef slic3r_GCodeProcessor_hpp_
+#define slic3r_GCodeProcessor_hpp_
+
+#if ENABLE_GCODE_VIEWER
+#include "libslic3r/GCodeReader.hpp"
+#include "libslic3r/Point.hpp"
+#include "libslic3r/ExtrusionEntity.hpp"
+#include "libslic3r/PrintConfig.hpp"
+#include "libslic3r/CustomGCode.hpp"
+
+#include
+#include
+#include
+
+namespace Slic3r {
+
+ enum class EMoveType : unsigned char
+ {
+ Noop,
+ Retract,
+ Unretract,
+ Tool_change,
+ Color_change,
+ Pause_Print,
+ Custom_GCode,
+ Travel,
+ Extrude,
+ Count
+ };
+
+ struct PrintEstimatedTimeStatistics
+ {
+ enum class ETimeMode : unsigned char
+ {
+ Normal,
+ Stealth,
+ Count
+ };
+
+ struct Mode
+ {
+ float time;
+ std::vector>> custom_gcode_times;
+ std::vector> moves_times;
+ std::vector> roles_times;
+ std::vector layers_times;
+
+ void reset() {
+ time = 0.0f;
+ custom_gcode_times.clear();
+ moves_times.clear();
+ roles_times.clear();
+ layers_times.clear();
+ }
+ };
+
+ std::array(ETimeMode::Count)> modes;
+
+ PrintEstimatedTimeStatistics() { reset(); }
+
+ void reset() {
+ for (auto m : modes) {
+ m.reset();
+ }
+ }
+ };
+
+ class GCodeProcessor
+ {
+ public:
+ static const std::string Extrusion_Role_Tag;
+ static const std::string Height_Tag;
+ static const std::string Layer_Change_Tag;
+ static const std::string Color_Change_Tag;
+ static const std::string Pause_Print_Tag;
+ static const std::string Custom_Code_Tag;
+ static const std::string First_Line_M73_Placeholder_Tag;
+ static const std::string Last_Line_M73_Placeholder_Tag;
+ static const std::string Estimated_Printing_Time_Placeholder_Tag;
+
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+ static const std::string Width_Tag;
+ static const std::string Mm3_Per_Mm_Tag;
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
+
+ private:
+ using AxisCoords = std::array;
+ using ExtruderColors = std::vector;
+
+ enum class EUnits : unsigned char
+ {
+ Millimeters,
+ Inches
+ };
+
+ enum class EPositioningType : unsigned char
+ {
+ Absolute,
+ Relative
+ };
+
+ struct CachedPosition
+ {
+ AxisCoords position; // mm
+ float feedrate; // mm/s
+
+ void reset();
+ };
+
+ struct CpColor
+ {
+ unsigned char counter;
+ unsigned char current;
+
+ void reset();
+ };
+
+ public:
+ struct FeedrateProfile
+ {
+ float entry{ 0.0f }; // mm/s
+ float cruise{ 0.0f }; // mm/s
+ float exit{ 0.0f }; // mm/s
+ };
+
+ struct Trapezoid
+ {
+ float accelerate_until{ 0.0f }; // mm
+ float decelerate_after{ 0.0f }; // mm
+ float cruise_feedrate{ 0.0f }; // mm/sec
+
+ float acceleration_time(float entry_feedrate, float acceleration) const;
+ float cruise_time() const;
+ float deceleration_time(float distance, float acceleration) const;
+ float cruise_distance() const;
+ };
+
+ struct TimeBlock
+ {
+ struct Flags
+ {
+ bool recalculate{ false };
+ bool nominal_length{ false };
+ };
+
+ EMoveType move_type{ EMoveType::Noop };
+ ExtrusionRole role{ erNone };
+ unsigned int layer_id{ 0 };
+ float distance{ 0.0f }; // mm
+ float acceleration{ 0.0f }; // mm/s^2
+ float max_entry_speed{ 0.0f }; // mm/s
+ float safe_feedrate{ 0.0f }; // mm/s
+ Flags flags;
+ FeedrateProfile feedrate_profile;
+ Trapezoid trapezoid;
+
+ // Calculates this block's trapezoid
+ void calculate_trapezoid();
+
+ float time() const;
+ };
+
+ private:
+ struct TimeMachine
+ {
+ struct State
+ {
+ float feedrate; // mm/s
+ float safe_feedrate; // mm/s
+ AxisCoords axis_feedrate; // mm/s
+ AxisCoords abs_axis_feedrate; // mm/s
+
+ void reset();
+ };
+
+ struct CustomGCodeTime
+ {
+ bool needed;
+ float cache;
+ std::vector> times;
+
+ void reset();
+ };
+
+ bool enabled;
+ float acceleration; // mm/s^2
+ // hard limit for the acceleration, to which the firmware will clamp.
+ float max_acceleration; // mm/s^2
+ float extrude_factor_override_percentage;
+ float time; // s
+ std::string line_m73_mask;
+ State curr;
+ State prev;
+ CustomGCodeTime gcode_time;
+ std::vector blocks;
+ std::vector g1_times_cache;
+ std::array(EMoveType::Count)> moves_time;
+ std::array(ExtrusionRole::erCount)> roles_time;
+ std::vector layers_time;
+
+ void reset();
+
+ // Simulates firmware st_synchronize() call
+ void simulate_st_synchronize(float additional_time = 0.0f);
+ void calculate_time(size_t keep_last_n_blocks = 0);
+ };
+
+ struct TimeProcessor
+ {
+ struct Planner
+ {
+ // Size of the firmware planner queue. The old 8-bit Marlins usually just managed 16 trapezoidal blocks.
+ // Let's be conservative and plan for newer boards with more memory.
+ static constexpr size_t queue_size = 64;
+ // The firmware recalculates last planner_queue_size trapezoidal blocks each time a new block is added.
+ // We are not simulating the firmware exactly, we calculate a sequence of blocks once a reasonable number of blocks accumulate.
+ static constexpr size_t refresh_threshold = queue_size * 4;
+ };
+
+ // extruder_id is currently used to correctly calculate filament load / unload times into the total print time.
+ // This is currently only really used by the MK3 MMU2:
+ // extruder_unloaded = true means no filament is loaded yet, all the filaments are parked in the MK3 MMU2 unit.
+ bool extruder_unloaded;
+ // whether or not to export post-process the gcode to export lines M73 in it
+ bool export_remaining_time_enabled;
+ // allow to skip the lines M201/M203/M204/M205 generated by GCode::print_machine_envelope()
+ bool machine_envelope_processing_enabled;
+ MachineEnvelopeConfig machine_limits;
+ // Additional load / unload times for a filament exchange sequence.
+ std::vector filament_load_times;
+ std::vector filament_unload_times;
+ std::array(PrintEstimatedTimeStatistics::ETimeMode::Count)> machines;
+
+ void reset();
+
+ // post process the file with the given filename to add remaining time lines M73
+ void post_process(const std::string& filename);
+ };
+
+ public:
+ struct MoveVertex
+ {
+ EMoveType type{ EMoveType::Noop };
+ ExtrusionRole extrusion_role{ erNone };
+ unsigned char extruder_id{ 0 };
+ unsigned char cp_color_id{ 0 };
+ Vec3f position{ Vec3f::Zero() }; // mm
+ float delta_extruder{ 0.0f }; // mm
+ float feedrate{ 0.0f }; // mm/s
+ float width{ 0.0f }; // mm
+ float height{ 0.0f }; // mm
+ float mm3_per_mm{ 0.0f };
+ float fan_speed{ 0.0f }; // percentage
+ float time{ 0.0f }; // s
+
+ float volumetric_rate() const { return feedrate * mm3_per_mm; }
+ };
+
+ struct Result
+ {
+ unsigned int id;
+ std::vector moves;
+ Pointfs bed_shape;
+ std::string printer_settings_id;
+ std::vector extruder_colors;
+ PrintEstimatedTimeStatistics time_statistics;
+
+#if ENABLE_GCODE_VIEWER_STATISTICS
+ long long time{ 0 };
+ void reset()
+ {
+ time = 0;
+ moves = std::vector();
+ bed_shape = Pointfs();
+ extruder_colors = std::vector();
+ }
+#else
+ void reset()
+ {
+ moves = std::vector();
+ bed_shape = Pointfs();
+ extruder_colors = std::vector();
+ }
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+ };
+
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+ struct DataChecker
+ {
+ struct Error
+ {
+ float value;
+ float tag_value;
+ ExtrusionRole role;
+ };
+
+ std::string type;
+ float threshold{ 0.01f };
+ float last_tag_value{ 0.0f };
+ unsigned int count{ 0 };
+ std::vector errors;
+
+ DataChecker(const std::string& type, float threshold)
+ : type(type), threshold(threshold)
+ {}
+
+ void update(float value, ExtrusionRole role) {
+ ++count;
+ if (last_tag_value != 0.0f) {
+ if (std::abs(value - last_tag_value) / last_tag_value > threshold)
+ errors.push_back({ value, last_tag_value, role });
+ }
+ }
+
+ void reset() { last_tag_value = 0.0f; errors.clear(); count = 0; }
+
+ std::pair get_min() const {
+ float delta_min = FLT_MAX;
+ float perc_min = 0.0f;
+ for (const Error& e : errors) {
+ if (delta_min > e.value - e.tag_value) {
+ delta_min = e.value - e.tag_value;
+ perc_min = 100.0f * delta_min / e.tag_value;
+ }
+ }
+ return { delta_min, perc_min };
+ }
+
+ std::pair get_max() const {
+ float delta_max = -FLT_MAX;
+ float perc_max = 0.0f;
+ for (const Error& e : errors) {
+ if (delta_max < e.value - e.tag_value) {
+ delta_max = e.value - e.tag_value;
+ perc_max = 100.0f * delta_max / e.tag_value;
+ }
+ }
+ return { delta_max, perc_max };
+ }
+
+ void output() const {
+ if (!errors.empty()) {
+ std::cout << type << ":\n";
+ std::cout << "Errors: " << errors.size() << " (" << 100.0f * float(errors.size()) / float(count) << "%)\n";
+ auto [min, perc_min] = get_min();
+ auto [max, perc_max] = get_max();
+ std::cout << "min: " << min << "(" << perc_min << "%) - max: " << max << "(" << perc_max << "%)\n";
+ }
+ }
+ };
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
+
+ private:
+ GCodeReader m_parser;
+
+ EUnits m_units;
+ EPositioningType m_global_positioning_type;
+ EPositioningType m_e_local_positioning_type;
+ std::vector m_extruder_offsets;
+ GCodeFlavor m_flavor;
+
+ AxisCoords m_start_position; // mm
+ AxisCoords m_end_position; // mm
+ AxisCoords m_origin; // mm
+ CachedPosition m_cached_position;
+
+ float m_feedrate; // mm/s
+ float m_width; // mm
+ float m_height; // mm
+ float m_mm3_per_mm;
+ float m_fan_speed; // percentage
+ ExtrusionRole m_extrusion_role;
+ unsigned char m_extruder_id;
+ ExtruderColors m_extruder_colors;
+ std::vector m_filament_diameters;
+ float m_extruded_last_z;
+ unsigned int m_layer_id;
+ CpColor m_cp_color;
+
+ enum class EProducer
+ {
+ Unknown,
+ PrusaSlicer,
+ Cura,
+ Simplify3D,
+ CraftWare,
+ ideaMaker
+ };
+
+ static const std::vector> Producers;
+ EProducer m_producer;
+ bool m_producers_enabled;
+
+ TimeProcessor m_time_processor;
+
+ Result m_result;
+ static unsigned int s_result_id;
+
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+ DataChecker m_mm3_per_mm_compare{ "mm3_per_mm", 0.01f };
+ DataChecker m_height_compare{ "height", 0.01f };
+ DataChecker m_width_compare{ "width", 0.01f };
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
+
+ public:
+ GCodeProcessor();
+
+ void apply_config(const PrintConfig& config);
+ void apply_config(const DynamicPrintConfig& config);
+ void enable_stealth_time_estimator(bool enabled);
+ bool is_stealth_time_estimator_enabled() const {
+ return m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].enabled;
+ }
+ void enable_machine_envelope_processing(bool enabled) { m_time_processor.machine_envelope_processing_enabled = enabled; }
+ void enable_producers(bool enabled) { m_producers_enabled = enabled; }
+ void reset();
+
+ const Result& get_result() const { return m_result; }
+ Result&& extract_result() { return std::move(m_result); }
+
+ // Process the gcode contained in the file with the given filename
+ void process_file(const std::string& filename);
+
+ float get_time(PrintEstimatedTimeStatistics::ETimeMode mode) const;
+ std::string get_time_dhm(PrintEstimatedTimeStatistics::ETimeMode mode) const;
+ std::vector>> get_custom_gcode_times(PrintEstimatedTimeStatistics::ETimeMode mode, bool include_remaining) const;
+
+ std::vector> get_moves_time(PrintEstimatedTimeStatistics::ETimeMode mode) const;
+ std::vector> get_roles_time(PrintEstimatedTimeStatistics::ETimeMode mode) const;
+ std::vector get_layers_time(PrintEstimatedTimeStatistics::ETimeMode mode) const;
+
+ private:
+ void process_gcode_line(const GCodeReader::GCodeLine& line);
+
+ // Process tags embedded into comments
+ void process_tags(const std::string& comment);
+ bool process_producers_tags(const std::string& comment);
+ bool process_prusaslicer_tags(const std::string& comment);
+ bool process_cura_tags(const std::string& comment);
+ bool process_simplify3d_tags(const std::string& comment);
+ bool process_craftware_tags(const std::string& comment);
+ bool process_ideamaker_tags(const std::string& comment);
+
+ bool detect_producer(const std::string& comment);
+
+ // Move
+ void process_G0(const GCodeReader::GCodeLine& line);
+ void process_G1(const GCodeReader::GCodeLine& line);
+
+ // Retract
+ void process_G10(const GCodeReader::GCodeLine& line);
+
+ // Unretract
+ void process_G11(const GCodeReader::GCodeLine& line);
+
+ // Set Units to Inches
+ void process_G20(const GCodeReader::GCodeLine& line);
+
+ // Set Units to Millimeters
+ void process_G21(const GCodeReader::GCodeLine& line);
+
+ // Firmware controlled Retract
+ void process_G22(const GCodeReader::GCodeLine& line);
+
+ // Firmware controlled Unretract
+ void process_G23(const GCodeReader::GCodeLine& line);
+
+ // Set to Absolute Positioning
+ void process_G90(const GCodeReader::GCodeLine& line);
+
+ // Set to Relative Positioning
+ void process_G91(const GCodeReader::GCodeLine& line);
+
+ // Set Position
+ void process_G92(const GCodeReader::GCodeLine& line);
+
+ // Sleep or Conditional stop
+ void process_M1(const GCodeReader::GCodeLine& line);
+
+ // Set extruder to absolute mode
+ void process_M82(const GCodeReader::GCodeLine& line);
+
+ // Set extruder to relative mode
+ void process_M83(const GCodeReader::GCodeLine& line);
+
+ // Set fan speed
+ void process_M106(const GCodeReader::GCodeLine& line);
+
+ // Disable fan
+ void process_M107(const GCodeReader::GCodeLine& line);
+
+ // Set tool (Sailfish)
+ void process_M108(const GCodeReader::GCodeLine& line);
+
+ // Recall stored home offsets
+ void process_M132(const GCodeReader::GCodeLine& line);
+
+ // Set tool (MakerWare)
+ void process_M135(const GCodeReader::GCodeLine& line);
+
+ // Set max printing acceleration
+ void process_M201(const GCodeReader::GCodeLine& line);
+
+ // Set maximum feedrate
+ void process_M203(const GCodeReader::GCodeLine& line);
+
+ // Set default acceleration
+ void process_M204(const GCodeReader::GCodeLine& line);
+
+ // Advanced settings
+ void process_M205(const GCodeReader::GCodeLine& line);
+
+ // Set extrude factor override percentage
+ void process_M221(const GCodeReader::GCodeLine& line);
+
+ // Repetier: Store x, y and z position
+ void process_M401(const GCodeReader::GCodeLine& line);
+
+ // Repetier: Go to stored position
+ void process_M402(const GCodeReader::GCodeLine& line);
+
+ // Set allowable instantaneous speed change
+ void process_M566(const GCodeReader::GCodeLine& line);
+
+ // Unload the current filament into the MK3 MMU2 unit at the end of print.
+ void process_M702(const GCodeReader::GCodeLine& line);
+
+ // Processes T line (Select Tool)
+ void process_T(const GCodeReader::GCodeLine& line);
+ void process_T(const std::string& command);
+
+ void store_move_vertex(EMoveType type);
+
+ float minimum_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, float feedrate) const;
+ float minimum_travel_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, float feedrate) const;
+ float get_axis_max_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const;
+ float get_axis_max_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const;
+ float get_axis_max_jerk(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const;
+ float get_retract_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const;
+ float get_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const;
+ void set_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, float value);
+ float get_filament_load_time(size_t extruder_id);
+ float get_filament_unload_time(size_t extruder_id);
+
+ void process_custom_gcode_time(CustomGCode::Type code);
+
+ // Simulates firmware st_synchronize() call
+ void simulate_st_synchronize(float additional_time = 0.0f);
+
+ void update_estimated_times_stats();
+ };
+
+} /* namespace Slic3r */
+
+#endif // ENABLE_GCODE_VIEWER
+
+#endif /* slic3r_GCodeProcessor_hpp_ */
+
+
diff --git a/src/libslic3r/GCode/PreviewData.cpp b/src/libslic3r/GCode/PreviewData.cpp
index 551c133450..8aec327db3 100644
--- a/src/libslic3r/GCode/PreviewData.cpp
+++ b/src/libslic3r/GCode/PreviewData.cpp
@@ -5,6 +5,8 @@
#include
+#if !ENABLE_GCODE_VIEWER
+
//! macro used to mark string used at localization,
#define L(s) (s)
@@ -516,3 +518,5 @@ Color operator * (float f, const Color& color)
}
} // namespace Slic3r
+
+#endif // !ENABLE_GCODE_VIEWER
diff --git a/src/libslic3r/GCode/PreviewData.hpp b/src/libslic3r/GCode/PreviewData.hpp
index 35bbfa50ac..930c1659e3 100644
--- a/src/libslic3r/GCode/PreviewData.hpp
+++ b/src/libslic3r/GCode/PreviewData.hpp
@@ -1,6 +1,8 @@
#ifndef slic3r_GCode_PreviewData_hpp_
#define slic3r_GCode_PreviewData_hpp_
+#if !ENABLE_GCODE_VIEWER
+
#include "../libslic3r.h"
#include "../ExtrusionEntity.hpp"
#include "../Point.hpp"
@@ -56,8 +58,7 @@ public:
// Color mapping to convert a float into a smooth rainbow of 10 colors.
class RangeBase
{
- public:
-
+ public:
virtual void reset() = 0;
virtual bool empty() const = 0;
virtual float min() const = 0;
@@ -73,7 +74,7 @@ public:
// Color mapping converting a float in a range between a min and a max into a smooth rainbow of 10 colors.
class Range : public RangeBase
{
- public:
+ public:
Range();
// RangeBase Overrides
@@ -97,8 +98,7 @@ public:
template
class MultiRange : public RangeBase
{
- public:
-
+ public:
void reset() override
{
bounds = decltype(bounds){};
@@ -160,8 +160,7 @@ public:
mode.set(static_cast(range_type_value), enable);
}
- private:
-
+ private:
// Interval bounds
struct Bounds
{
@@ -394,4 +393,6 @@ public:
} // namespace Slic3r
+#endif // !ENABLE_GCODE_VIEWER
+
#endif /* slic3r_GCode_PreviewData_hpp_ */
diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp
index c0f778687c..0752b6dfcb 100644
--- a/src/libslic3r/GCode/WipeTower.cpp
+++ b/src/libslic3r/GCode/WipeTower.cpp
@@ -21,7 +21,11 @@ TODO LIST
#include
#include
+#if ENABLE_GCODE_VIEWER
+#include "GCodeProcessor.hpp"
+#else
#include "Analyzer.hpp"
+#endif // ENABLE_GCODE_VIEWER
#include "BoundingBox.hpp"
#if defined(__linux) || defined(__GNUC__ )
@@ -47,36 +51,69 @@ public:
m_extrusion_flow(0.f),
m_preview_suppressed(false),
m_elapsed_time(0.f),
+#if !ENABLE_GCODE_VIEWER || ENABLE_GCODE_VIEWER_DATA_CHECKING
m_default_analyzer_line_width(line_width),
+#endif // !ENABLE_GCODE_VIEWER || ENABLE_GCODE_VIEWER_DATA_CHECKING
m_gcode_flavor(flavor),
m_filpar(filament_parameters)
{
// adds tag for analyzer:
char buf[64];
+#if ENABLE_GCODE_VIEWER
+ sprintf(buf, ";%s%f\n", GCodeProcessor::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%s\n", GCodeProcessor::Extrusion_Role_Tag.c_str(), ExtrusionEntity::role_to_string(erWipeTower).c_str());
+ m_gcode += buf;
+#else
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;
+#endif // ENABLE_GCODE_VIEWER
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
change_analyzer_line_width(line_width);
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
}
- WipeTowerWriter& 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;
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+ WipeTowerWriter& change_analyzer_line_width(float line_width) {
+ // adds tag for analyzer:
+ char buf[64];
+ sprintf(buf, ";%s%f\n", GCodeProcessor::Width_Tag.c_str(), line_width);
+ m_gcode += buf;
+ return *this;
}
- WipeTowerWriter& change_analyzer_mm3_per_mm(float len, float e) {
- static const float area = float(M_PI) * 1.75f * 1.75f / 4.f;
- float mm3_per_mm = (len == 0.f ? 0.f : area * e / len);
- // adds tag for analyzer:
- char buf[64];
- sprintf(buf, ";%s%f\n", GCodeAnalyzer::Mm3_Per_Mm_Tag.c_str(), mm3_per_mm);
- m_gcode += buf;
- return *this;
+ WipeTowerWriter& change_analyzer_mm3_per_mm(float len, float e) {
+ static const float area = float(M_PI) * 1.75f * 1.75f / 4.f;
+ float mm3_per_mm = (len == 0.f ? 0.f : area * e / len);
+ // adds tag for processor:
+ char buf[64];
+ sprintf(buf, ";%s%f\n", GCodeProcessor::Mm3_Per_Mm_Tag.c_str(), mm3_per_mm);
+ m_gcode += buf;
+ return *this;
}
+#else
+#if !ENABLE_GCODE_VIEWER
+ WipeTowerWriter& 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;
+ }
+
+ WipeTowerWriter& change_analyzer_mm3_per_mm(float len, float e) {
+ static const float area = float(M_PI) * 1.75f * 1.75f / 4.f;
+ float mm3_per_mm = (len == 0.f ? 0.f : area * e / len);
+ // adds tag for analyzer:
+ char buf[64];
+ sprintf(buf, ";%s%f\n", GCodeAnalyzer::Mm3_Per_Mm_Tag.c_str(), mm3_per_mm);
+ m_gcode += buf;
+ return *this;
+ }
+#endif // !ENABLE_GCODE_VIEWER
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
WipeTowerWriter& set_initial_position(const Vec2f &pos, float width = 0.f, float depth = 0.f, float internal_angle = 0.f) {
m_wipe_tower_width = width;
@@ -111,8 +148,13 @@ public:
// 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.
- WipeTowerWriter& suppress_preview() { change_analyzer_line_width(0.f); m_preview_suppressed = true; return *this; }
- WipeTowerWriter& resume_preview() { change_analyzer_line_width(m_default_analyzer_line_width); m_preview_suppressed = false; return *this; }
+#if !ENABLE_GCODE_VIEWER || ENABLE_GCODE_VIEWER_DATA_CHECKING
+ WipeTowerWriter& suppress_preview() { change_analyzer_line_width(0.f); m_preview_suppressed = true; return *this; }
+ WipeTowerWriter& resume_preview() { change_analyzer_line_width(m_default_analyzer_line_width); m_preview_suppressed = false; return *this; }
+#else
+ WipeTowerWriter& suppress_preview() { m_preview_suppressed = true; return *this; }
+ WipeTowerWriter& resume_preview() { m_preview_suppressed = false; return *this; }
+#endif // !ENABLE_GCODE_VIEWER || ENABLE_GCODE_VIEWER_DATA_CHECKING
WipeTowerWriter& feedrate(float f)
{
@@ -149,8 +191,14 @@ public:
Vec2f rot(this->rotate(Vec2f(x,y))); // this is where we want to go
if (! m_preview_suppressed && e > 0.f && len > 0.f) {
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
change_analyzer_mm3_per_mm(len, e);
- // Width of a squished extrusion, corrected for the roundings of the squished extrusions.
+#else
+#if !ENABLE_GCODE_VIEWER
+ change_analyzer_mm3_per_mm(len, e);
+#endif // !ENABLE_GCODE_VIEWER
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
+ // 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 = e * m_filpar[0].filament_area / (len * m_layer_height);
// Correct for the roundings of a squished extrusion.
@@ -411,7 +459,9 @@ private:
float m_wipe_tower_depth = 0.f;
unsigned m_last_fan_speed = 0;
int current_temp = -1;
+#if !ENABLE_GCODE_VIEWER || ENABLE_GCODE_VIEWER_DATA_CHECKING
const float m_default_analyzer_line_width;
+#endif // !ENABLE_GCODE_VIEWER || ENABLE_GCODE_VIEWER_DATA_CHECKING
float m_used_filament_length = 0.f;
GCodeFlavor m_gcode_flavor;
const std::vector& m_filpar;
@@ -852,8 +902,12 @@ void WipeTower::toolchange_Unload(
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
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
writer.append("; CP TOOLCHANGE UNLOAD\n")
- .change_analyzer_line_width(line_width);
+ .change_analyzer_line_width(line_width);
+#else
+ writer.append("; CP TOOLCHANGE UNLOAD\n");
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
unsigned i = 0; // iterates through ramming_speed
m_left_to_right = true; // current direction of ramming
@@ -930,7 +984,9 @@ void WipeTower::toolchange_Unload(
}
}
Vec2f end_of_ramming(writer.x(),writer.y());
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
writer.change_analyzer_line_width(m_perimeter_width); // so the next lines are not affected by ramming_line_width_multiplier
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
// Retraction:
float old_x = writer.x();
diff --git a/src/libslic3r/GCodeReader.cpp b/src/libslic3r/GCodeReader.cpp
index e68bc5ad29..ab77b01413 100644
--- a/src/libslic3r/GCodeReader.cpp
+++ b/src/libslic3r/GCodeReader.cpp
@@ -115,7 +115,12 @@ void GCodeReader::parse_file(const std::string &file, callback_t callback)
{
std::ifstream f(file);
std::string line;
+#if ENABLE_GCODE_VIEWER
+ m_parsing_file = true;
+ while (m_parsing_file && std::getline(f, line))
+#else
while (std::getline(f, line))
+#endif // ENABLE_GCODE_VIEWER
this->parse_line(line, callback);
}
diff --git a/src/libslic3r/GCodeReader.hpp b/src/libslic3r/GCodeReader.hpp
index 9503ddcc16..7e0793cd9b 100644
--- a/src/libslic3r/GCodeReader.hpp
+++ b/src/libslic3r/GCodeReader.hpp
@@ -107,6 +107,9 @@ public:
{ GCodeLine gline; this->parse_line(line.c_str(), gline, callback); }
void parse_file(const std::string &file, callback_t callback);
+#if ENABLE_GCODE_VIEWER
+ void quit_parsing_file() { m_parsing_file = false; }
+#endif // ENABLE_GCODE_VIEWER
float& x() { return m_position[X]; }
float x() const { return m_position[X]; }
@@ -145,6 +148,9 @@ private:
char m_extrusion_axis;
float m_position[NUM_AXES];
bool m_verbose;
+#if ENABLE_GCODE_VIEWER
+ bool m_parsing_file{ false };
+#endif // ENABLE_GCODE_VIEWER
};
} /* namespace Slic3r */
diff --git a/src/libslic3r/GCodeTimeEstimator.cpp b/src/libslic3r/GCodeTimeEstimator.cpp
index 9e8137ef0e..aa9ee2f643 100644
--- a/src/libslic3r/GCodeTimeEstimator.cpp
+++ b/src/libslic3r/GCodeTimeEstimator.cpp
@@ -9,6 +9,8 @@
#include
#include
+#if !ENABLE_GCODE_VIEWER
+
static const float MMMIN_TO_MMSEC = 1.0f / 60.0f;
static const float MILLISEC_TO_SEC = 0.001f;
static const float INCHES_TO_MM = 25.4f;
@@ -1671,3 +1673,5 @@ namespace Slic3r {
}
#endif // ENABLE_MOVE_STATS
}
+
+#endif // !ENABLE_GCODE_VIEWER
diff --git a/src/libslic3r/GCodeTimeEstimator.hpp b/src/libslic3r/GCodeTimeEstimator.hpp
index 63e11c4faa..0dd3407cb0 100644
--- a/src/libslic3r/GCodeTimeEstimator.hpp
+++ b/src/libslic3r/GCodeTimeEstimator.hpp
@@ -6,6 +6,8 @@
#include "GCodeReader.hpp"
#include "CustomGCode.hpp"
+#if !ENABLE_GCODE_VIEWER
+
#define ENABLE_MOVE_STATS 0
namespace Slic3r {
@@ -481,4 +483,6 @@ namespace Slic3r {
} /* namespace Slic3r */
+#endif // !ENABLE_GCODE_VIEWER
+
#endif /* slic3r_GCodeTimeEstimator_hpp_ */
diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp
index 3beb74f235..ad48bd6bd3 100644
--- a/src/libslic3r/Model.cpp
+++ b/src/libslic3r/Model.cpp
@@ -21,7 +21,9 @@
#include "SVG.hpp"
#include
#include "GCodeWriter.hpp"
+#if !ENABLE_GCODE_VIEWER
#include "GCode/PreviewData.hpp"
+#endif // !ENABLE_GCODE_VIEWER
namespace Slic3r {
diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp
index 7176ca81a4..a5160d2db3 100644
--- a/src/libslic3r/Preset.cpp
+++ b/src/libslic3r/Preset.cpp
@@ -1812,6 +1812,26 @@ namespace PresetUtils {
}
return out;
}
+
+#if ENABLE_GCODE_VIEWER
+ std::string system_printer_bed_model(const Preset& preset)
+ {
+ std::string out;
+ const VendorProfile::PrinterModel* pm = PresetUtils::system_printer_model(preset);
+ if (pm != nullptr && !pm->bed_model.empty())
+ out = Slic3r::resources_dir() + "/profiles/" + preset.vendor->id + "/" + pm->bed_model;
+ return out;
+ }
+
+ std::string system_printer_bed_texture(const Preset& preset)
+ {
+ std::string out;
+ const VendorProfile::PrinterModel* pm = PresetUtils::system_printer_model(preset);
+ if (pm != nullptr && !pm->bed_texture.empty())
+ out = Slic3r::resources_dir() + "/profiles/" + preset.vendor->id + "/" + pm->bed_texture;
+ return out;
+ }
+#endif // ENABLE_GCODE_VIEWER
} // namespace PresetUtils
} // namespace Slic3r
diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp
index e34fca4dd7..30edfc859a 100644
--- a/src/libslic3r/Preset.hpp
+++ b/src/libslic3r/Preset.hpp
@@ -527,6 +527,10 @@ public:
namespace PresetUtils {
// PrinterModel of a system profile, from which this preset is derived, or null if it is not derived from a system profile.
const VendorProfile::PrinterModel* system_printer_model(const Preset &preset);
+#if ENABLE_GCODE_VIEWER
+ std::string system_printer_bed_model(const Preset& preset);
+ std::string system_printer_bed_texture(const Preset& preset);
+#endif // ENABLE_GCODE_VIEWER
} // namespace PresetUtils
diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp
index 0c8a11fcf0..7924f18906 100644
--- a/src/libslic3r/Print.cpp
+++ b/src/libslic3r/Print.cpp
@@ -1632,13 +1632,21 @@ void Print::process()
// The export_gcode may die for various reasons (fails to process output_filename_format,
// write error into the G-code, cannot execute post-processing scripts).
// It is up to the caller to show an error message.
+#if ENABLE_GCODE_VIEWER
+std::string Print::export_gcode(const std::string& path_template, GCodeProcessor::Result* result, ThumbnailsGeneratorCallback thumbnail_cb)
+#else
std::string Print::export_gcode(const std::string& path_template, GCodePreviewData* preview_data, ThumbnailsGeneratorCallback thumbnail_cb)
+#endif // ENABLE_GCODE_VIEWER
{
// output everything to a G-code file
// The following call may die if the output_filename_format template substitution fails.
std::string path = this->output_filepath(path_template);
std::string message;
+#if ENABLE_GCODE_VIEWER
+ if (!path.empty() && result == nullptr) {
+#else
if (! path.empty() && preview_data == nullptr) {
+#endif // ENABLE_GCODE_VIEWER
// Only show the path if preview_data is not set -> running from command line.
message = L("Exporting G-code");
message += " to ";
@@ -1649,7 +1657,11 @@ std::string Print::export_gcode(const std::string& path_template, GCodePreviewDa
// The following line may die for multiple reasons.
GCode gcode;
+#if ENABLE_GCODE_VIEWER
+ gcode.do_export(this, path.c_str(), result, thumbnail_cb);
+#else
gcode.do_export(this, path.c_str(), preview_data, thumbnail_cb);
+#endif // ENABLE_GCODE_VIEWER
return path.c_str();
}
@@ -2180,16 +2192,16 @@ DynamicConfig PrintStatistics::config() const
DynamicConfig config;
std::string normal_print_time = short_time(this->estimated_normal_print_time);
std::string silent_print_time = short_time(this->estimated_silent_print_time);
- config.set_key_value("print_time", new ConfigOptionString(normal_print_time));
- config.set_key_value("normal_print_time", new ConfigOptionString(normal_print_time));
- config.set_key_value("silent_print_time", new ConfigOptionString(silent_print_time));
- config.set_key_value("used_filament", new ConfigOptionFloat (this->total_used_filament / 1000.));
- config.set_key_value("extruded_volume", new ConfigOptionFloat (this->total_extruded_volume));
- config.set_key_value("total_cost", new ConfigOptionFloat (this->total_cost));
+ config.set_key_value("print_time", new ConfigOptionString(normal_print_time));
+ config.set_key_value("normal_print_time", new ConfigOptionString(normal_print_time));
+ config.set_key_value("silent_print_time", new ConfigOptionString(silent_print_time));
+ config.set_key_value("used_filament", new ConfigOptionFloat(this->total_used_filament / 1000.));
+ config.set_key_value("extruded_volume", new ConfigOptionFloat(this->total_extruded_volume));
+ config.set_key_value("total_cost", new ConfigOptionFloat(this->total_cost));
config.set_key_value("total_toolchanges", new ConfigOptionInt(this->total_toolchanges));
- config.set_key_value("total_weight", new ConfigOptionFloat (this->total_weight));
- config.set_key_value("total_wipe_tower_cost", new ConfigOptionFloat (this->total_wipe_tower_cost));
- config.set_key_value("total_wipe_tower_filament", new ConfigOptionFloat (this->total_wipe_tower_filament));
+ config.set_key_value("total_weight", new ConfigOptionFloat(this->total_weight));
+ config.set_key_value("total_wipe_tower_cost", new ConfigOptionFloat(this->total_wipe_tower_cost));
+ config.set_key_value("total_wipe_tower_filament", new ConfigOptionFloat(this->total_wipe_tower_filament));
return config;
}
diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp
index 05929dd2ef..da98bba5f2 100644
--- a/src/libslic3r/Print.hpp
+++ b/src/libslic3r/Print.hpp
@@ -11,6 +11,9 @@
#include "GCode/ToolOrdering.hpp"
#include "GCode/WipeTower.hpp"
#include "GCode/ThumbnailData.hpp"
+#if ENABLE_GCODE_VIEWER
+#include "GCode/GCodeProcessor.hpp"
+#endif // ENABLE_GCODE_VIEWER
#include "libslic3r.h"
@@ -20,7 +23,9 @@ class Print;
class PrintObject;
class ModelObject;
class GCode;
+#if !ENABLE_GCODE_VIEWER
class GCodePreviewData;
+#endif // !ENABLE_GCODE_VIEWER
enum class SlicingMode : uint32_t;
class Layer;
class SupportLayer;
@@ -300,8 +305,10 @@ struct PrintStatistics
PrintStatistics() { clear(); }
std::string estimated_normal_print_time;
std::string estimated_silent_print_time;
+#if !ENABLE_GCODE_VIEWER
std::vector> estimated_normal_custom_gcode_print_times;
std::vector